mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Rebase durable task feature branch with main (#2806)
This commit is contained in:
@@ -89,7 +89,7 @@ public class AIProjectClientFixture : IChatClientAgentFixture
|
||||
List<ChatMessage> messages = [];
|
||||
await foreach (AgentResponseItem item in this._client.GetProjectOpenAIClient().GetProjectConversationsClient().GetProjectConversationItemsAsync(conversationId, order: "asc"))
|
||||
{
|
||||
var openAIItem = item.AsOpenAIResponseItem();
|
||||
var openAIItem = item.AsResponseResultItem();
|
||||
if (openAIItem is MessageResponseItem messageItem)
|
||||
{
|
||||
messages.Add(new ChatMessage
|
||||
|
||||
@@ -214,13 +214,31 @@ public class AIAgentTests
|
||||
[Fact]
|
||||
public void ValidateAgentIDIsIdempotent()
|
||||
{
|
||||
// Arrange
|
||||
var agent = new MockAgent();
|
||||
|
||||
// Act
|
||||
string id = agent.Id;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(id);
|
||||
Assert.Equal(id, agent.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateAgentIDCanBeProvidedByDerivedAgentClass()
|
||||
{
|
||||
// Arrange
|
||||
var agent = new MockAgent(id: "test-agent-id");
|
||||
|
||||
// Act
|
||||
string id = agent.Id;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(id);
|
||||
Assert.Equal("test-agent-id", id);
|
||||
}
|
||||
|
||||
#region GetService Method Tests
|
||||
|
||||
/// <summary>
|
||||
@@ -344,6 +362,13 @@ public class AIAgentTests
|
||||
|
||||
private sealed class MockAgent : AIAgent
|
||||
{
|
||||
public MockAgent(string? id = null)
|
||||
{
|
||||
this.IdCore = id;
|
||||
}
|
||||
|
||||
protected override string? IdCore { get; }
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
@@ -31,7 +32,7 @@ public class DelegatingAIAgentTests
|
||||
this._testThread = new TestAgentThread();
|
||||
|
||||
// Setup inner agent mock
|
||||
this._innerAgentMock.Setup(x => x.Id).Returns("test-agent-id");
|
||||
this._innerAgentMock.Protected().SetupGet<string>("IdCore").Returns("test-agent-id");
|
||||
this._innerAgentMock.Setup(x => x.Name).Returns("Test Agent");
|
||||
this._innerAgentMock.Setup(x => x.Description).Returns("Test Description");
|
||||
this._innerAgentMock.Setup(x => x.GetNewThread()).Returns(this._testThread);
|
||||
@@ -93,7 +94,7 @@ public class DelegatingAIAgentTests
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-agent-id", id);
|
||||
this._innerAgentMock.Verify(x => x.Id, Times.Once);
|
||||
this._innerAgentMock.Protected().VerifyGet<string>("IdCore", Times.Once());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+3
-1
@@ -10,7 +10,6 @@ using Azure.Core;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Agents.AI.CosmosNoSql.UnitTests;
|
||||
|
||||
@@ -59,6 +58,9 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Fail fast if emulator is not available
|
||||
this.SkipIfEmulatorNotAvailable();
|
||||
|
||||
// Check environment variable to determine if we should preserve containers
|
||||
// Set COSMOS_PRESERVE_CONTAINERS=true to keep containers and data for inspection
|
||||
this._preserveContainer = string.Equals(Environment.GetEnvironmentVariable("COSMOS_PRESERVE_CONTAINERS"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
+3
-1
@@ -7,7 +7,6 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Checkpointing;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Agents.AI.CosmosNoSql.UnitTests;
|
||||
|
||||
@@ -58,6 +57,9 @@ public class CosmosCheckpointStoreTests : IAsyncLifetime, IDisposable
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Fail fast if emulator is not available
|
||||
this.SkipIfEmulatorNotAvailable();
|
||||
|
||||
// Check environment variable to determine if we should preserve containers
|
||||
// Set COSMOS_PRESERVE_CONTAINERS=true to keep containers and data for inspection
|
||||
this._preserveContainer = string.Equals(Environment.GetEnvironmentVariable("COSMOS_PRESERVE_CONTAINERS"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -81,6 +81,64 @@ public sealed class AgentEntityTests(ITestOutputHelper outputHelper) : IDisposab
|
||||
Assert.Null(request.OrchestrationId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("run")]
|
||||
[InlineData("Run")]
|
||||
[InlineData("RunAgentAsync")]
|
||||
public async Task RunAgentMethodNamesAllWorkAsync(string runAgentMethodName)
|
||||
{
|
||||
// Setup
|
||||
AIAgent simpleAgent = TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
|
||||
name: "TestAgent",
|
||||
instructions: "You are a helpful assistant that always responds with a friendly greeting."
|
||||
);
|
||||
|
||||
using TestHelper testHelper = TestHelper.Start([simpleAgent], this._outputHelper);
|
||||
|
||||
// A proxy agent is needed to call the hosted test agent
|
||||
AIAgent simpleAgentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services);
|
||||
|
||||
AgentThread thread = simpleAgentProxy.GetNewThread();
|
||||
|
||||
DurableTaskClient client = testHelper.GetClient();
|
||||
|
||||
AgentSessionId sessionId = thread.GetService<AgentSessionId>();
|
||||
EntityInstanceId expectedEntityId = new($"dafx-{simpleAgent.Name}", sessionId.Key);
|
||||
|
||||
EntityMetadata? entity = await client.Entities.GetEntityAsync(expectedEntityId, false, this.TestTimeoutToken);
|
||||
|
||||
Assert.Null(entity);
|
||||
|
||||
// Act: send a prompt to the agent
|
||||
await client.Entities.SignalEntityAsync(
|
||||
expectedEntityId,
|
||||
runAgentMethodName,
|
||||
new RunRequest("Hello!"),
|
||||
cancellation: this.TestTimeoutToken);
|
||||
|
||||
while (!this.TestTimeoutToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(500, this.TestTimeoutToken);
|
||||
|
||||
// Assert: verify the agent state was stored with the correct entity name prefix
|
||||
entity = await client.Entities.GetEntityAsync(expectedEntityId, true, this.TestTimeoutToken);
|
||||
|
||||
if (entity is not null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotNull(entity);
|
||||
Assert.True(entity.IncludesState);
|
||||
|
||||
DurableAgentState state = entity.State.ReadAs<DurableAgentState>();
|
||||
|
||||
DurableAgentStateRequest request = Assert.Single(state.Data.ConversationHistory.OfType<DurableAgentStateRequest>());
|
||||
|
||||
Assert.Null(request.OrchestrationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OrchestrationIdSetDuringOrchestrationAsync()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Microsoft.Agents.AI.DurableTask.State;
|
||||
using Microsoft.DurableTask.Client;
|
||||
using Microsoft.DurableTask.Client.Entities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using OpenAI.Chat;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for Time-To-Live (TTL) functionality of durable agent entities.
|
||||
/// </summary>
|
||||
[Collection("Sequential")]
|
||||
[Trait("Category", "Integration")]
|
||||
public sealed class TimeToLiveTests(ITestOutputHelper outputHelper) : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan s_defaultTimeout = Debugger.IsAttached
|
||||
? TimeSpan.FromMinutes(5)
|
||||
: TimeSpan.FromSeconds(30);
|
||||
|
||||
private static readonly IConfiguration s_configuration =
|
||||
new ConfigurationBuilder()
|
||||
.AddUserSecrets(Assembly.GetExecutingAssembly())
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
private readonly ITestOutputHelper _outputHelper = outputHelper;
|
||||
private readonly CancellationTokenSource _cts = new(delay: s_defaultTimeout);
|
||||
|
||||
private CancellationToken TestTimeoutToken => this._cts.Token;
|
||||
|
||||
public void Dispose() => this._cts.Dispose();
|
||||
|
||||
[Fact]
|
||||
public async Task EntityExpiresAfterTTLAsync()
|
||||
{
|
||||
// Arrange: Create agent with short TTL (10 seconds)
|
||||
TimeSpan ttl = TimeSpan.FromSeconds(10);
|
||||
AIAgent simpleAgent = TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
|
||||
name: "TTLTestAgent",
|
||||
instructions: "You are a helpful assistant."
|
||||
);
|
||||
|
||||
using TestHelper testHelper = TestHelper.Start(
|
||||
this._outputHelper,
|
||||
options =>
|
||||
{
|
||||
options.DefaultTimeToLive = ttl;
|
||||
options.MinimumTimeToLiveSignalDelay = TimeSpan.FromSeconds(1);
|
||||
options.AddAIAgent(simpleAgent);
|
||||
});
|
||||
|
||||
AIAgent agentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services);
|
||||
AgentThread thread = agentProxy.GetNewThread();
|
||||
DurableTaskClient client = testHelper.GetClient();
|
||||
AgentSessionId sessionId = thread.GetService<AgentSessionId>();
|
||||
|
||||
// Act: Send a message to the agent
|
||||
await agentProxy.RunAsync(
|
||||
message: "Hello!",
|
||||
thread,
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
// Verify entity exists and get expiration time
|
||||
EntityMetadata? entity = await client.Entities.GetEntityAsync(sessionId, true, this.TestTimeoutToken);
|
||||
Assert.NotNull(entity);
|
||||
Assert.True(entity.IncludesState);
|
||||
|
||||
DurableAgentState state = entity.State.ReadAs<DurableAgentState>();
|
||||
Assert.NotNull(state.Data.ExpirationTimeUtc);
|
||||
DateTime expirationTime = state.Data.ExpirationTimeUtc.Value;
|
||||
Assert.True(expirationTime > DateTime.UtcNow);
|
||||
|
||||
// Calculate how long to wait: expiration time + buffer for signal processing
|
||||
TimeSpan waitTime = expirationTime - DateTime.UtcNow + TimeSpan.FromSeconds(1);
|
||||
if (waitTime > TimeSpan.Zero)
|
||||
{
|
||||
await Task.Delay(waitTime, this.TestTimeoutToken);
|
||||
}
|
||||
|
||||
// Poll the entity state until it's deleted (with timeout)
|
||||
DateTime pollTimeout = DateTime.UtcNow.AddSeconds(10);
|
||||
bool entityDeleted = false;
|
||||
while (DateTime.UtcNow < pollTimeout && !entityDeleted)
|
||||
{
|
||||
entity = await client.Entities.GetEntityAsync(sessionId, true, this.TestTimeoutToken);
|
||||
entityDeleted = entity is null;
|
||||
|
||||
if (!entityDeleted)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), this.TestTimeoutToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Assert: Verify entity state is deleted
|
||||
Assert.True(entityDeleted, "Entity should have been deleted after TTL expiration");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EntityTTLResetsOnInteractionAsync()
|
||||
{
|
||||
// Arrange: Create agent with short TTL
|
||||
TimeSpan ttl = TimeSpan.FromSeconds(6);
|
||||
AIAgent simpleAgent = TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
|
||||
name: "TTLResetTestAgent",
|
||||
instructions: "You are a helpful assistant."
|
||||
);
|
||||
|
||||
using TestHelper testHelper = TestHelper.Start(
|
||||
this._outputHelper,
|
||||
options =>
|
||||
{
|
||||
options.DefaultTimeToLive = ttl;
|
||||
options.MinimumTimeToLiveSignalDelay = TimeSpan.FromSeconds(1);
|
||||
options.AddAIAgent(simpleAgent);
|
||||
});
|
||||
|
||||
AIAgent agentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services);
|
||||
AgentThread thread = agentProxy.GetNewThread();
|
||||
DurableTaskClient client = testHelper.GetClient();
|
||||
AgentSessionId sessionId = thread.GetService<AgentSessionId>();
|
||||
|
||||
// Act: Send first message
|
||||
await agentProxy.RunAsync(
|
||||
message: "Hello!",
|
||||
thread,
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
EntityMetadata? entity = await client.Entities.GetEntityAsync(sessionId, true, this.TestTimeoutToken);
|
||||
Assert.NotNull(entity);
|
||||
Assert.True(entity.IncludesState);
|
||||
|
||||
DurableAgentState state = entity.State.ReadAs<DurableAgentState>();
|
||||
DateTime firstExpirationTime = state.Data.ExpirationTimeUtc!.Value;
|
||||
|
||||
// Wait partway through TTL
|
||||
await Task.Delay(TimeSpan.FromSeconds(3), this.TestTimeoutToken);
|
||||
|
||||
// Send second message (should reset TTL)
|
||||
await agentProxy.RunAsync(
|
||||
message: "Hello again!",
|
||||
thread,
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
// Verify expiration time was updated
|
||||
entity = await client.Entities.GetEntityAsync(sessionId, true, this.TestTimeoutToken);
|
||||
Assert.NotNull(entity);
|
||||
Assert.True(entity.IncludesState);
|
||||
|
||||
state = entity.State.ReadAs<DurableAgentState>();
|
||||
DateTime secondExpirationTime = state.Data.ExpirationTimeUtc!.Value;
|
||||
Assert.True(secondExpirationTime > firstExpirationTime);
|
||||
|
||||
// Calculate when the original expiration time would have been
|
||||
DateTime originalExpirationTime = firstExpirationTime;
|
||||
TimeSpan waitUntilOriginalExpiration = originalExpirationTime - DateTime.UtcNow + TimeSpan.FromSeconds(2);
|
||||
|
||||
if (waitUntilOriginalExpiration > TimeSpan.Zero)
|
||||
{
|
||||
await Task.Delay(waitUntilOriginalExpiration, this.TestTimeoutToken);
|
||||
}
|
||||
|
||||
// Assert: Entity should still exist because TTL was reset
|
||||
// The new expiration time should be in the future
|
||||
entity = await client.Entities.GetEntityAsync(sessionId, true, this.TestTimeoutToken);
|
||||
Assert.NotNull(entity);
|
||||
Assert.True(entity.IncludesState);
|
||||
|
||||
state = entity.State.ReadAs<DurableAgentState>();
|
||||
Assert.NotNull(state);
|
||||
Assert.NotNull(state.Data.ExpirationTimeUtc);
|
||||
Assert.True(
|
||||
state.Data.ExpirationTimeUtc > DateTime.UtcNow,
|
||||
"Entity should still be valid because TTL was reset");
|
||||
|
||||
// Wait for the entity to be deleted
|
||||
DateTime pollTimeout = DateTime.UtcNow.AddSeconds(10);
|
||||
bool entityDeleted = false;
|
||||
while (DateTime.UtcNow < pollTimeout && !entityDeleted)
|
||||
{
|
||||
entity = await client.Entities.GetEntityAsync(sessionId, true, this.TestTimeoutToken);
|
||||
entityDeleted = entity is null;
|
||||
|
||||
if (!entityDeleted)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), this.TestTimeoutToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Assert: Entity should have been deleted
|
||||
Assert.True(entityDeleted, "Entity should have been deleted after TTL expiration");
|
||||
}
|
||||
}
|
||||
+4
-16
@@ -276,15 +276,9 @@ public sealed class BasicStreamingTests : IAsyncDisposable
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated via dependency injection")]
|
||||
internal sealed class FakeChatClientAgent : AIAgent
|
||||
{
|
||||
public FakeChatClientAgent()
|
||||
{
|
||||
this.Id = "fake-agent";
|
||||
this.Description = "A fake agent for testing";
|
||||
}
|
||||
protected override string? IdCore => "fake-agent";
|
||||
|
||||
public override string Id { get; }
|
||||
|
||||
public override string? Description { get; }
|
||||
public override string? Description => "A fake agent for testing";
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
{
|
||||
@@ -350,15 +344,9 @@ internal sealed class FakeChatClientAgent : AIAgent
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated via dependency injection")]
|
||||
internal sealed class FakeMultiMessageAgent : AIAgent
|
||||
{
|
||||
public FakeMultiMessageAgent()
|
||||
{
|
||||
this.Id = "fake-multi-message-agent";
|
||||
this.Description = "A fake agent that sends multiple messages for testing";
|
||||
}
|
||||
protected override string? IdCore => "fake-multi-message-agent";
|
||||
|
||||
public override string Id { get; }
|
||||
|
||||
public override string? Description { get; }
|
||||
public override string? Description => "A fake agent that sends multiple messages for testing";
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
{
|
||||
|
||||
+2
-2
@@ -421,7 +421,7 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests
|
||||
|
||||
private sealed class MultiResponseAgent : AIAgent
|
||||
{
|
||||
public override string Id => "multi-response-agent";
|
||||
protected override string? IdCore => "multi-response-agent";
|
||||
|
||||
public override string? Description => "Agent that produces multiple text chunks";
|
||||
|
||||
@@ -510,7 +510,7 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests
|
||||
|
||||
private sealed class TestAgent : AIAgent
|
||||
{
|
||||
public override string Id => "test-agent";
|
||||
protected override string? IdCore => "test-agent";
|
||||
|
||||
public override string? Description => "Test agent";
|
||||
|
||||
|
||||
+55
-55
@@ -49,7 +49,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "One Two Three";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Count to 3");
|
||||
@@ -90,10 +90,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Hello! How can I help you today?";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Hello");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Hello");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -117,7 +117,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "This is a test response with multiple words";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -162,12 +162,12 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
(Agent1Name, Agent1Instructions, Agent1Response),
|
||||
(Agent2Name, Agent2Instructions, Agent2Response));
|
||||
|
||||
OpenAIResponseClient responseClient1 = this.CreateResponseClient(Agent1Name);
|
||||
OpenAIResponseClient responseClient2 = this.CreateResponseClient(Agent2Name);
|
||||
ResponsesClient responseClient1 = this.CreateResponseClient(Agent1Name);
|
||||
ResponsesClient responseClient2 = this.CreateResponseClient(Agent2Name);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response1 = await responseClient1.CreateResponseAsync("Hello");
|
||||
OpenAIResponse response2 = await responseClient2.CreateResponseAsync("Hello");
|
||||
ResponseResult response1 = await responseClient1.CreateResponseAsync("Hello");
|
||||
ResponseResult response2 = await responseClient2.CreateResponseAsync("Hello");
|
||||
|
||||
// Assert
|
||||
string content1 = response1.GetOutputText();
|
||||
@@ -190,10 +190,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "This is the response";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act - Non-streaming
|
||||
OpenAIResponse nonStreamingResponse = await responseClient.CreateResponseAsync("Test");
|
||||
ResponseResult nonStreamingResponse = await responseClient.CreateResponseAsync("Test");
|
||||
|
||||
// Act - Streaming
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -224,10 +224,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Complete";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Test");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Test");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ResponseStatus.Completed, response.Status);
|
||||
@@ -247,7 +247,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Test response with multiple words";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -286,7 +286,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -316,10 +316,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Response with metadata";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Test");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Test");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response.Id);
|
||||
@@ -340,7 +340,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
string expectedResponse = string.Join(" ", Enumerable.Range(1, 100).Select(i => $"Word{i}"));
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, expectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Generate long text");
|
||||
@@ -371,7 +371,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Test output index";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -407,7 +407,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Hello";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -437,7 +437,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Hello! How are you? I'm fine. 100% great!";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -467,10 +467,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Symbols: @#$%^&*() Quotes: \"Hello\" 'World' Unicode: 你好 🌍";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Test");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Test");
|
||||
|
||||
// Assert
|
||||
string content = response.GetOutputText();
|
||||
@@ -489,7 +489,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Testing item IDs";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -525,12 +525,12 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Response";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act & Assert - Make 5 sequential requests
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync($"Request {i}");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync($"Request {i}");
|
||||
Assert.NotNull(response);
|
||||
Assert.Equal(ResponseStatus.Completed, response.Status);
|
||||
Assert.Equal(ExpectedResponse, response.GetOutputText());
|
||||
@@ -549,7 +549,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Streaming response";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act & Assert - Make 3 sequential streaming requests
|
||||
for (int i = 0; i < 3; i++)
|
||||
@@ -581,13 +581,13 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Response";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
List<string> responseIds = [];
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync($"Request {i}");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync($"Request {i}");
|
||||
responseIds.Add(response.Id);
|
||||
}
|
||||
|
||||
@@ -608,7 +608,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Test sequence numbers with multiple words";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -641,10 +641,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Test model info";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Test");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Test");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response.Model);
|
||||
@@ -663,7 +663,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Hello, world! How are you today? I'm doing well.";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -693,10 +693,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "OK";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Hi");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Hi");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -716,7 +716,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Test content indices";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -748,10 +748,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Line 1\nLine 2\nLine 3";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Test");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Test");
|
||||
|
||||
// Assert
|
||||
string content = response.GetOutputText();
|
||||
@@ -771,7 +771,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "First line\nSecond line\nThird line";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -807,10 +807,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
instructions: Instructions,
|
||||
chatClient: new TestHelpers.ImageContentMockChatClient(ImageUrl));
|
||||
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Show me an image");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Show me an image");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -834,7 +834,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
instructions: Instructions,
|
||||
chatClient: new TestHelpers.ImageContentMockChatClient(ImageUrl));
|
||||
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Show me an image");
|
||||
@@ -868,10 +868,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
instructions: Instructions,
|
||||
chatClient: new TestHelpers.AudioContentMockChatClient(AudioData, Transcript));
|
||||
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Generate audio");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Generate audio");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -896,7 +896,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
instructions: Instructions,
|
||||
chatClient: new TestHelpers.AudioContentMockChatClient(AudioData, Transcript));
|
||||
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Generate audio");
|
||||
@@ -930,10 +930,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
instructions: Instructions,
|
||||
chatClient: new TestHelpers.FunctionCallMockChatClient(FunctionName, Arguments));
|
||||
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("What's the weather?");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("What's the weather?");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -957,7 +957,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
instructions: Instructions,
|
||||
chatClient: new TestHelpers.FunctionCallMockChatClient(FunctionName, Arguments));
|
||||
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Calculate 2+2");
|
||||
@@ -988,10 +988,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
instructions: Instructions,
|
||||
chatClient: new TestHelpers.MixedContentMockChatClient());
|
||||
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
OpenAIResponse response = await responseClient.CreateResponseAsync("Show me various content");
|
||||
ResponseResult response = await responseClient.CreateResponseAsync("Show me various content");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -1014,7 +1014,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
instructions: Instructions,
|
||||
chatClient: new TestHelpers.MixedContentMockChatClient());
|
||||
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Show me various content");
|
||||
@@ -1047,7 +1047,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Complete text response";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -1075,7 +1075,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
const string ExpectedResponse = "Response with content parts";
|
||||
|
||||
this._httpClient = await this.CreateTestServerAsync(AgentName, Instructions, ExpectedResponse);
|
||||
OpenAIResponseClient responseClient = this.CreateResponseClient(AgentName);
|
||||
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
|
||||
|
||||
// Act
|
||||
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
|
||||
@@ -1122,7 +1122,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
string conversationId = convDoc.RootElement.GetProperty("id").GetString()!;
|
||||
|
||||
// Act - Send request with conversation ID using raw HTTP
|
||||
// (OpenAI SDK doesn't expose ConversationId directly on ResponseCreationOptions)
|
||||
// (OpenAI SDK doesn't expose ConversationId directly on CreateResponseOptions)
|
||||
var requestBody = new
|
||||
{
|
||||
input = "Test",
|
||||
@@ -1201,9 +1201,9 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
|
||||
Assert.Null(mockChatClient.LastChatOptions.ConversationId);
|
||||
}
|
||||
|
||||
private OpenAIResponseClient CreateResponseClient(string agentName)
|
||||
private ResponsesClient CreateResponseClient(string agentName)
|
||||
{
|
||||
return new OpenAIResponseClient(
|
||||
return new ResponsesClient(
|
||||
model: "test-model",
|
||||
credential: new ApiKeyCredential("test-api-key"),
|
||||
options: new OpenAIClientOptions
|
||||
|
||||
+3
-3
@@ -55,9 +55,9 @@ public sealed class OpenAIResponseClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test OpenAIResponseClient implementation for testing.
|
||||
/// Creates a test ResponsesClient implementation for testing.
|
||||
/// </summary>
|
||||
private sealed class TestOpenAIResponseClient : OpenAIResponseClient
|
||||
private sealed class TestOpenAIResponseClient : ResponsesClient
|
||||
{
|
||||
public TestOpenAIResponseClient()
|
||||
{
|
||||
@@ -147,7 +147,7 @@ public sealed class OpenAIResponseClientExtensionsTests
|
||||
{
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||
((OpenAIResponseClient)null!).CreateAIAgent());
|
||||
((ResponsesClient)null!).CreateAIAgent());
|
||||
|
||||
Assert.Equal("client", exception.ParamName);
|
||||
}
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ public sealed class MediaInputTest(ITestOutputHelper output) : IntegrationTest(o
|
||||
private const string ImageReference = "https://sample-files.com/downloads/images/jpg/web_optimized_1200x800_97kb.jpg";
|
||||
|
||||
[Theory]
|
||||
[InlineData(ImageReference, "image/jpeg")]
|
||||
[InlineData(ImageReference, "image/jpeg", Skip = "Failing consistently in the agent service api")]
|
||||
[InlineData(PdfReference, "application/pdf", Skip = "Not currently supported by agent service api")]
|
||||
public async Task ValidateFileUrlAsync(string fileSource, string mediaType)
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ public class EdgeMapSmokeTests
|
||||
|
||||
Dictionary<string, HashSet<Edge>> workflowEdges = [];
|
||||
|
||||
FanInEdgeData edgeData = new(["executor1", "executor2"], "executor3", new EdgeId(0));
|
||||
FanInEdgeData edgeData = new(["executor1", "executor2"], "executor3", new EdgeId(0), null);
|
||||
Edge fanInEdge = new(edgeData);
|
||||
|
||||
workflowEdges["executor1"] = [fanInEdge];
|
||||
|
||||
@@ -155,7 +155,7 @@ public class EdgeRunnerTests
|
||||
runContext.Executors["executor2"] = new ForwardMessageExecutor<string>("executor2");
|
||||
runContext.Executors["executor3"] = new ForwardMessageExecutor<string>("executor3");
|
||||
|
||||
FanInEdgeData edgeData = new(["executor1", "executor2"], "executor3", new EdgeId(0));
|
||||
FanInEdgeData edgeData = new(["executor1", "executor2"], "executor3", new EdgeId(0), null);
|
||||
FanInEdgeRunner runner = new(runContext, edgeData);
|
||||
|
||||
// Step 1: Send message from executor1, should not forward yet.
|
||||
|
||||
@@ -118,7 +118,7 @@ public class JsonSerializationTests
|
||||
RunJsonRoundtrip(TestFanOutEdgeInfo_Assigner, predicate: TestFanOutEdgeInfo_Assigner.CreateValidator());
|
||||
}
|
||||
|
||||
private static FanInEdgeData TestFanInEdgeData => new(["SourceExecutor1", "SourceExecutor2"], "TargetExecutor", TakeEdgeId());
|
||||
private static FanInEdgeData TestFanInEdgeData => new(["SourceExecutor1", "SourceExecutor2"], "TargetExecutor", TakeEdgeId(), null);
|
||||
private static FanInEdgeInfo TestFanInEdgeInfo => new(TestFanInEdgeData);
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -137,17 +137,17 @@ public class RepresentationTests
|
||||
RunEdgeInfoMatchTest(fanOutEdgeWithAssigner);
|
||||
|
||||
// FanIn Edges
|
||||
Edge fanInEdge = new(new FanInEdgeData([Source(1), Source(2), Source(3)], Sink(1), TakeEdgeId()));
|
||||
Edge fanInEdge = new(new FanInEdgeData([Source(1), Source(2), Source(3)], Sink(1), TakeEdgeId(), null));
|
||||
RunEdgeInfoMatchTest(fanInEdge);
|
||||
|
||||
Edge fanInEdge2 = new(new FanInEdgeData([Source(1), Source(2), Source(3)], Sink(1), TakeEdgeId()));
|
||||
Edge fanInEdge2 = new(new FanInEdgeData([Source(1), Source(2), Source(3)], Sink(1), TakeEdgeId(), null));
|
||||
RunEdgeInfoMatchTest(fanInEdge, fanInEdge2);
|
||||
|
||||
Edge fanInEdge3 = new(new FanInEdgeData([Source(2), Source(3), Source(1)], Sink(1), TakeEdgeId()));
|
||||
Edge fanInEdge3 = new(new FanInEdgeData([Source(2), Source(3), Source(1)], Sink(1), TakeEdgeId(), null));
|
||||
RunEdgeInfoMatchTest(fanInEdge, fanInEdge3, expect: false); // Order matters (though for FanIn maybe it shouldn't?)
|
||||
|
||||
Edge fanInEdge4 = new(new FanInEdgeData([Source(1), Source(2), Source(4)], Sink(1), TakeEdgeId()));
|
||||
Edge fanInEdge5 = new(new FanInEdgeData([Source(1), Source(2), Source(3)], Sink(2), TakeEdgeId()));
|
||||
Edge fanInEdge4 = new(new FanInEdgeData([Source(1), Source(2), Source(4)], Sink(1), TakeEdgeId(), null));
|
||||
Edge fanInEdge5 = new(new FanInEdgeData([Source(1), Source(2), Source(3)], Sink(2), TakeEdgeId(), null));
|
||||
RunEdgeInfoMatchTest(fanInEdge, fanInEdge4, expect: false); // Identity matters
|
||||
RunEdgeInfoMatchTest(fanInEdge, fanInEdge5, expect: false);
|
||||
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ internal sealed class HelloAgent(string id = nameof(HelloAgent)) : AIAgent
|
||||
public const string Greeting = "Hello World!";
|
||||
public const string DefaultId = nameof(HelloAgent);
|
||||
|
||||
public override string Id => id;
|
||||
protected override string? IdCore => id;
|
||||
public override string? Name => id;
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public class SpecializedExecutorSmokeTests
|
||||
{
|
||||
public class TestAIAgent(List<ChatMessage>? messages = null, string? id = null, string? name = null) : AIAgent
|
||||
{
|
||||
public override string Id => id ?? base.Id;
|
||||
protected override string? IdCore => id;
|
||||
public override string? Name => name;
|
||||
|
||||
public static List<ChatMessage> ToChatMessages(params string[] messages)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Microsoft.Agents.AI.Workflows.UnitTests;
|
||||
|
||||
internal class TestEchoAgent(string? id = null, string? name = null, string? prefix = null) : AIAgent
|
||||
{
|
||||
public override string Id => id ?? base.Id;
|
||||
protected override string? IdCore => id;
|
||||
public override string? Name => name ?? base.Name;
|
||||
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
|
||||
@@ -394,4 +394,61 @@ public class WorkflowVisualizerTests
|
||||
// Check fan-in (should have intermediate node)
|
||||
mermaidContent.Should().Contain("((fan-in))");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_WorkflowViz_Mermaid_Edge_Label_With_Pipe()
|
||||
{
|
||||
// Test that pipe characters in labels are properly escaped
|
||||
var start = new MockExecutor("start");
|
||||
var end = new MockExecutor("end");
|
||||
|
||||
var workflow = new WorkflowBuilder("start")
|
||||
.AddEdge(start, end, label: "High | Low Priority")
|
||||
.Build();
|
||||
|
||||
var mermaidContent = workflow.ToMermaidString();
|
||||
|
||||
// Should escape pipe character
|
||||
mermaidContent.Should().Contain("start -->|High | Low Priority| end");
|
||||
// Should not contain unescaped pipe that would break syntax
|
||||
mermaidContent.Should().NotContain("-->|High | Low");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_WorkflowViz_Mermaid_Edge_Label_With_Special_Chars()
|
||||
{
|
||||
// Test that special characters are properly escaped
|
||||
var start = new MockExecutor("start");
|
||||
var end = new MockExecutor("end");
|
||||
|
||||
var workflow = new WorkflowBuilder("start")
|
||||
.AddEdge(start, end, label: "Score >= 90 & < 100")
|
||||
.Build();
|
||||
|
||||
var mermaidContent = workflow.ToMermaidString();
|
||||
|
||||
// Should escape special characters
|
||||
mermaidContent.Should().Contain("&");
|
||||
mermaidContent.Should().Contain(">");
|
||||
mermaidContent.Should().Contain("<");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_WorkflowViz_Mermaid_Edge_Label_With_Newline()
|
||||
{
|
||||
// Test that newlines are converted to <br/>
|
||||
var start = new MockExecutor("start");
|
||||
var end = new MockExecutor("end");
|
||||
|
||||
var workflow = new WorkflowBuilder("start")
|
||||
.AddEdge(start, end, label: "Line 1\nLine 2")
|
||||
.Build();
|
||||
|
||||
var mermaidContent = workflow.ToMermaidString();
|
||||
|
||||
// Should convert newline to <br/>
|
||||
mermaidContent.Should().Contain("Line 1<br/>Line 2");
|
||||
// Should not contain literal newline in the label (but the overall output has newlines between statements)
|
||||
mermaidContent.Should().NotContain("Line 1\nLine 2");
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -3,11 +3,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace OpenAIResponse.IntegrationTests;
|
||||
namespace ResponseResult.IntegrationTests;
|
||||
|
||||
public class OpenAIResponseStoreTrueChatClientAgentRunStreamingTests() : ChatClientAgentRunStreamingTests<OpenAIResponseFixture>(() => new(store: true))
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
private const string SkipReason = "ResponseResult does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync() =>
|
||||
@@ -16,7 +16,7 @@ public class OpenAIResponseStoreTrueChatClientAgentRunStreamingTests() : ChatCli
|
||||
|
||||
public class OpenAIResponseStoreFalseChatClientAgentRunStreamingTests() : ChatClientAgentRunStreamingTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
private const string SkipReason = "ResponseResult does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync() =>
|
||||
|
||||
+3
-3
@@ -3,11 +3,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace OpenAIResponse.IntegrationTests;
|
||||
namespace ResponseResult.IntegrationTests;
|
||||
|
||||
public class OpenAIResponseStoreTrueChatClientAgentRunTests() : ChatClientAgentRunTests<OpenAIResponseFixture>(() => new(store: true))
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
private const string SkipReason = "ResponseResult does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync() =>
|
||||
@@ -16,7 +16,7 @@ public class OpenAIResponseStoreTrueChatClientAgentRunTests() : ChatClientAgentR
|
||||
|
||||
public class OpenAIResponseStoreFalseChatClientAgentRunTests() : ChatClientAgentRunTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
private const string SkipReason = "ResponseResult does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync() =>
|
||||
|
||||
@@ -12,13 +12,13 @@ using OpenAI;
|
||||
using OpenAI.Responses;
|
||||
using Shared.IntegrationTests;
|
||||
|
||||
namespace OpenAIResponse.IntegrationTests;
|
||||
namespace ResponseResult.IntegrationTests;
|
||||
|
||||
public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
|
||||
{
|
||||
private static readonly OpenAIConfiguration s_config = TestConfiguration.LoadSection<OpenAIConfiguration>();
|
||||
|
||||
private OpenAIResponseClient _openAIResponseClient = null!;
|
||||
private ResponsesClient _openAIResponseClient = null!;
|
||||
private ChatClientAgent _agent = null!;
|
||||
|
||||
public AIAgent Agent => this._agent;
|
||||
@@ -77,7 +77,7 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
|
||||
{
|
||||
Instructions = instructions,
|
||||
Tools = aiTools,
|
||||
RawRepresentationFactory = new Func<IChatClient, object>(_ => new ResponseCreationOptions() { StoredOutputEnabled = store })
|
||||
RawRepresentationFactory = new Func<IChatClient, object>(_ => new CreateResponseOptions() { StoredOutputEnabled = store })
|
||||
},
|
||||
});
|
||||
|
||||
@@ -92,7 +92,7 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
this._openAIResponseClient = new OpenAIClient(s_config.ApiKey)
|
||||
.GetOpenAIResponseClient(s_config.ChatModelId);
|
||||
.GetResponsesClient(s_config.ChatModelId);
|
||||
|
||||
this._agent = await this.CreateChatClientAgentAsync();
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace OpenAIResponse.IntegrationTests;
|
||||
namespace ResponseResult.IntegrationTests;
|
||||
|
||||
public class OpenAIResponseStoreTrueRunStreamingTests() : RunStreamingTests<OpenAIResponseFixture>(() => new(store: true))
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
private const string SkipReason = "ResponseResult does not support empty messages";
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
Task.CompletedTask;
|
||||
@@ -15,7 +15,7 @@ public class OpenAIResponseStoreTrueRunStreamingTests() : RunStreamingTests<Open
|
||||
|
||||
public class OpenAIResponseStoreFalseRunStreamingTests() : RunStreamingTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
private const string SkipReason = "ResponseResult does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace OpenAIResponse.IntegrationTests;
|
||||
namespace ResponseResult.IntegrationTests;
|
||||
|
||||
public class OpenAIResponseStoreTrueRunTests() : RunTests<OpenAIResponseFixture>(() => new(store: true))
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
private const string SkipReason = "ResponseResult does not support empty messages";
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
Task.CompletedTask;
|
||||
@@ -15,7 +15,7 @@ public class OpenAIResponseStoreTrueRunTests() : RunTests<OpenAIResponseFixture>
|
||||
|
||||
public class OpenAIResponseStoreFalseRunTests() : RunTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
private const string SkipReason = "ResponseResult does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
|
||||
Reference in New Issue
Block a user