Merge branch 'main' into javiercn/mapagui-hosting-overloads

This commit is contained in:
Javier Calvarro Nelson
2025-12-05 13:46:45 +01:00
committed by GitHub
Unverified
242 changed files with 13019 additions and 6985 deletions
@@ -13,8 +13,6 @@
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
</ItemGroup>
@@ -84,8 +84,7 @@ public class AnthropicChatCompletionFixture : IChatClientAgentFixture
return Task.FromResult(new ChatClientAgent(chatClient, options: new()
{
Name = name,
Instructions = instructions,
ChatOptions = new() { Tools = aiTools }
ChatOptions = new() { Instructions = instructions, Tools = aiTools }
}));
}
@@ -37,16 +37,20 @@ public class AIProjectClientCreateTests
{
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
model: s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
name: AgentName,
description: AgentDescription)),
options: new ChatClientAgentOptions()
{
Name = AgentName,
Description = AgentDescription,
ChatOptions = new() { Instructions = AgentInstructions }
}),
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
model: s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
name: AgentName,
description: AgentDescription)),
options: new ChatClientAgentOptions()
{
Name = AgentName,
Description = AgentDescription,
ChatOptions = new() { Instructions = AgentInstructions }
}),
"CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
name: AgentName,
creationOptions: new AgentVersionCreationOptions(new PromptAgentDefinition(s_config.DeploymentName) { Instructions = AgentInstructions }) { Description = AgentDescription }),
@@ -239,16 +243,18 @@ public class AIProjectClientCreateTests
{
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
model: s_config.DeploymentName,
options: new ChatClientAgentOptions(
name: AgentName,
instructions: AgentInstructions,
tools: [weatherFunction])),
options: new ChatClientAgentOptions()
{
Name = AgentName,
ChatOptions = new() { Instructions = AgentInstructions, Tools = [weatherFunction] }
}),
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
name: AgentName,
instructions: AgentInstructions,
tools: [weatherFunction])),
options: new ChatClientAgentOptions()
{
Name = AgentName,
ChatOptions = new() { Instructions = AgentInstructions, Tools = [weatherFunction] }
}),
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
};
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests.Support;
@@ -34,16 +35,20 @@ public class AzureAIAgentsPersistentCreateTests
{
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
name: AgentName,
description: AgentDescription)),
options: new ChatClientAgentOptions()
{
ChatOptions = new() { Instructions = AgentInstructions },
Name = AgentName,
Description = AgentDescription
}),
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
name: AgentName,
description: AgentDescription)),
options: new ChatClientAgentOptions()
{
ChatOptions = new() { Instructions = AgentInstructions },
Name = AgentName,
Description = AgentDescription
}),
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
instructions: AgentInstructions,
@@ -104,19 +109,32 @@ public class AzureAIAgentsPersistentCreateTests
);
var vectorStoreMetadata = await this._persistentAgentsClient.VectorStores.CreateVectorStoreAsync([uploadedAgentFile.Id], name: "WordCodeLookup_VectorStore");
// Wait for vector store indexing to complete before using it
await this.WaitForVectorStoreReadyAsync(this._persistentAgentsClient, vectorStoreMetadata.Value.Id);
// Act.
var agent = createMechanism switch
{
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = AgentInstructions,
Tools = [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]
}
}),
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = AgentInstructions,
Tools = [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]
}
}),
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
instructions: AgentInstructions,
@@ -179,15 +197,24 @@ public class AzureAIAgentsPersistentCreateTests
// Hosted tool path (tools supplied via ChatClientAgentOptions)
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = AgentInstructions,
Tools = [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]
}
}),
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }])),
// Foundry (definitions + resources provided directly)
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = AgentInstructions,
Tools = [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]
}
}),
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
instructions: AgentInstructions,
@@ -232,14 +259,24 @@ public class AzureAIAgentsPersistentCreateTests
{
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
tools: [weatherFunction])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = AgentInstructions,
Tools = [weatherFunction]
}
}),
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
s_config.DeploymentName,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
tools: [weatherFunction])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = AgentInstructions,
Tools = [weatherFunction]
}
}),
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
};
@@ -259,4 +296,42 @@ public class AzureAIAgentsPersistentCreateTests
await this._persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
}
}
/// <summary>
/// Waits for a vector store to complete indexing by polling its status.
/// </summary>
/// <param name="client">The persistent agents client.</param>
/// <param name="vectorStoreId">The ID of the vector store.</param>
/// <param name="maxWaitSeconds">Maximum time to wait in seconds (default: 30).</param>
/// <returns>A task that completes when the vector store is ready or throws on timeout/failure.</returns>
private async Task WaitForVectorStoreReadyAsync(
PersistentAgentsClient client,
string vectorStoreId,
int maxWaitSeconds = 30)
{
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < maxWaitSeconds)
{
PersistentAgentsVectorStore vectorStore = await client.VectorStores.GetVectorStoreAsync(vectorStoreId);
if (vectorStore.Status == VectorStoreStatus.Completed)
{
if (vectorStore.FileCounts.Failed > 0)
{
throw new InvalidOperationException("Vector store indexing failed for some files");
}
return;
}
if (vectorStore.Status == VectorStoreStatus.Expired)
{
throw new InvalidOperationException("Vector store has expired");
}
await Task.Delay(1000);
}
throw new TimeoutException($"Vector store did not complete indexing within {maxWaitSeconds}s");
}
}
@@ -1,10 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="System.Net.ServerSentEvents" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
</ItemGroup>
@@ -1,9 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="System.Net.ServerSentEvents" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="FluentAssertions" />
</ItemGroup>
@@ -13,8 +13,6 @@
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
</ItemGroup>
@@ -91,7 +91,7 @@ public sealed class AnthropicBetaServiceExtensionsTests
{
Name = "Test Agent",
Description = "Test description",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -158,7 +158,7 @@ public sealed class AnthropicClientExtensionsTests
{
Name = "Test Agent",
Description = "Test description",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -310,7 +310,7 @@ public sealed class PersistentAgentsClientExtensionsTests
{
Name = "Override Name",
Description = "Override Description",
Instructions = "Override Instructions"
ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -337,7 +337,7 @@ public sealed class PersistentAgentsClientExtensionsTests
{
Name = "Override Name",
Description = "Override Description",
Instructions = "Override Instructions"
ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -386,7 +386,7 @@ public sealed class PersistentAgentsClientExtensionsTests
{
Name = "Override Name",
Description = "Override Description",
Instructions = "Override Instructions"
ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -413,7 +413,7 @@ public sealed class PersistentAgentsClientExtensionsTests
{
Name = "Override Name",
Description = "Override Description",
Instructions = "Override Instructions"
ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -557,7 +557,7 @@ public sealed class PersistentAgentsClientExtensionsTests
{
Name = "Test Agent",
Description = "Test description",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -584,7 +584,7 @@ public sealed class PersistentAgentsClientExtensionsTests
{
Name = "Test Agent",
Description = "Test description",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -752,7 +752,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -775,7 +775,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
TestChatClient? testChatClient = null;
@@ -803,7 +803,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -826,7 +826,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
TestChatClient? testChatClient = null;
@@ -1575,8 +1575,8 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Custom instructions",
Description = "Custom description"
Description = "Custom description",
ChatOptions = new ChatOptions { Instructions = "Custom instructions" }
};
// Act
@@ -1610,8 +1610,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test",
ChatOptions = new ChatOptions { Tools = tools }
ChatOptions = new ChatOptions { Instructions = "Test", Tools = tools }
};
// Act
@@ -49,8 +49,7 @@ public class AzureAIProjectChatClientTests
new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test instructions",
ChatOptions = new() { ConversationId = "conv_12345" }
ChatOptions = new() { Instructions = "Test instructions", ConversationId = "conv_12345" }
});
// Act
@@ -99,7 +98,7 @@ public class AzureAIProjectChatClientTests
new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test instructions",
ChatOptions = new() { Instructions = "Test instructions" },
});
// Act
@@ -148,8 +147,7 @@ public class AzureAIProjectChatClientTests
new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test instructions",
ChatOptions = new() { ConversationId = "conv_should_not_use_default" }
ChatOptions = new() { Instructions = "Test instructions", ConversationId = "conv_should_not_use_default" }
});
// Act
@@ -198,7 +196,7 @@ public class AzureAIProjectChatClientTests
new ChatClientAgentOptions
{
Name = "test-agent",
Instructions = "Test instructions",
ChatOptions = new() { Instructions = "Test instructions" },
});
// Act
@@ -70,7 +70,7 @@ public sealed class ChatClientAgentFactoryTests
Assert.IsType<ChatClientAgent>(agent);
var chatClientAgent = agent as ChatClientAgent;
Assert.NotNull(chatClientAgent?.ChatOptions);
Assert.Equal("Provide detailed and accurate responses.", chatClientAgent?.ChatOptions?.Instructions);
Assert.Equal("You are a helpful assistant.", chatClientAgent?.ChatOptions?.Instructions);
Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.Temperature);
Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.FrequencyPenalty);
Assert.Equal(1024, chatClientAgent?.ChatOptions?.MaxOutputTokens);
@@ -0,0 +1,358 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Http;
using System.Net.ServerSentEvents;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests;
public sealed class ForwardedPropertiesTests : IAsyncDisposable
{
private WebApplication? _app;
private HttpClient? _client;
[Fact]
public async Task ForwardedProps_AreParsedAndPassedToAgent_WhenProvidedInRequestAsync()
{
// Arrange
FakeForwardedPropsAgent fakeAgent = new();
await this.SetupTestServerAsync(fakeAgent);
// Create request JSON with forwardedProps (per AG-UI protocol spec)
const string RequestJson = """
{
"threadId": "thread-123",
"runId": "run-456",
"messages": [{ "id": "msg-1", "role": "user", "content": "test forwarded props" }],
"forwardedProps": { "customProp": "customValue", "sessionId": "test-session-123" }
}
""";
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
// Act
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
// Assert
response.IsSuccessStatusCode.Should().BeTrue();
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
fakeAgent.ReceivedForwardedProperties.GetProperty("customProp").GetString().Should().Be("customValue");
fakeAgent.ReceivedForwardedProperties.GetProperty("sessionId").GetString().Should().Be("test-session-123");
}
[Fact]
public async Task ForwardedProps_WithNestedObjects_AreCorrectlyParsedAsync()
{
// Arrange
FakeForwardedPropsAgent fakeAgent = new();
await this.SetupTestServerAsync(fakeAgent);
const string RequestJson = """
{
"threadId": "thread-123",
"runId": "run-456",
"messages": [{ "id": "msg-1", "role": "user", "content": "test nested props" }],
"forwardedProps": {
"user": { "id": "user-1", "name": "Test User" },
"metadata": { "version": "1.0", "feature": "test" }
}
}
""";
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
// Act
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
// Assert
response.IsSuccessStatusCode.Should().BeTrue();
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
JsonElement user = fakeAgent.ReceivedForwardedProperties.GetProperty("user");
user.GetProperty("id").GetString().Should().Be("user-1");
user.GetProperty("name").GetString().Should().Be("Test User");
JsonElement metadata = fakeAgent.ReceivedForwardedProperties.GetProperty("metadata");
metadata.GetProperty("version").GetString().Should().Be("1.0");
metadata.GetProperty("feature").GetString().Should().Be("test");
}
[Fact]
public async Task ForwardedProps_WithArrays_AreCorrectlyParsedAsync()
{
// Arrange
FakeForwardedPropsAgent fakeAgent = new();
await this.SetupTestServerAsync(fakeAgent);
const string RequestJson = """
{
"threadId": "thread-123",
"runId": "run-456",
"messages": [{ "id": "msg-1", "role": "user", "content": "test array props" }],
"forwardedProps": {
"tags": ["tag1", "tag2", "tag3"],
"scores": [1, 2, 3, 4, 5]
}
}
""";
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
// Act
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
// Assert
response.IsSuccessStatusCode.Should().BeTrue();
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
JsonElement tags = fakeAgent.ReceivedForwardedProperties.GetProperty("tags");
tags.GetArrayLength().Should().Be(3);
tags[0].GetString().Should().Be("tag1");
JsonElement scores = fakeAgent.ReceivedForwardedProperties.GetProperty("scores");
scores.GetArrayLength().Should().Be(5);
scores[2].GetInt32().Should().Be(3);
}
[Fact]
public async Task ForwardedProps_WhenEmpty_DoesNotCauseErrorsAsync()
{
// Arrange
FakeForwardedPropsAgent fakeAgent = new();
await this.SetupTestServerAsync(fakeAgent);
const string RequestJson = """
{
"threadId": "thread-123",
"runId": "run-456",
"messages": [{ "id": "msg-1", "role": "user", "content": "test empty props" }],
"forwardedProps": {}
}
""";
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
// Act
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
// Assert
response.IsSuccessStatusCode.Should().BeTrue();
}
[Fact]
public async Task ForwardedProps_WhenNotProvided_AgentStillWorksAsync()
{
// Arrange
FakeForwardedPropsAgent fakeAgent = new();
await this.SetupTestServerAsync(fakeAgent);
const string RequestJson = """
{
"threadId": "thread-123",
"runId": "run-456",
"messages": [{ "id": "msg-1", "role": "user", "content": "test no props" }]
}
""";
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
// Act
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
// Assert
response.IsSuccessStatusCode.Should().BeTrue();
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Undefined);
}
[Fact]
public async Task ForwardedProps_ReturnsValidSSEResponse_WithTextDeltaEventsAsync()
{
// Arrange
FakeForwardedPropsAgent fakeAgent = new();
await this.SetupTestServerAsync(fakeAgent);
const string RequestJson = """
{
"threadId": "thread-123",
"runId": "run-456",
"messages": [{ "id": "msg-1", "role": "user", "content": "test response" }],
"forwardedProps": { "customProp": "value" }
}
""";
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
// Act
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
response.EnsureSuccessStatusCode();
Stream stream = await response.Content.ReadAsStreamAsync();
List<SseItem<string>> events = [];
await foreach (SseItem<string> item in SseParser.Create(stream).EnumerateAsync())
{
events.Add(item);
}
// Assert
events.Should().NotBeEmpty();
// SSE events have EventType = "message" and the actual type is in the JSON data
// Should have run_started event
events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"RUN_STARTED\""));
// Should have text_message_start event
events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"TEXT_MESSAGE_START\""));
// Should have text_message_content event with the response text
events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"TEXT_MESSAGE_CONTENT\""));
// Should have run_finished event
events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"RUN_FINISHED\""));
}
[Fact]
public async Task ForwardedProps_WithMixedTypes_AreCorrectlyParsedAsync()
{
// Arrange
FakeForwardedPropsAgent fakeAgent = new();
await this.SetupTestServerAsync(fakeAgent);
const string RequestJson = """
{
"threadId": "thread-123",
"runId": "run-456",
"messages": [{ "id": "msg-1", "role": "user", "content": "test mixed types" }],
"forwardedProps": {
"stringProp": "text",
"numberProp": 42,
"boolProp": true,
"nullProp": null,
"arrayProp": [1, "two", false],
"objectProp": { "nested": "value" }
}
}
""";
using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
// Act
HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
// Assert
response.IsSuccessStatusCode.Should().BeTrue();
fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
fakeAgent.ReceivedForwardedProperties.GetProperty("stringProp").GetString().Should().Be("text");
fakeAgent.ReceivedForwardedProperties.GetProperty("numberProp").GetInt32().Should().Be(42);
fakeAgent.ReceivedForwardedProperties.GetProperty("boolProp").GetBoolean().Should().BeTrue();
fakeAgent.ReceivedForwardedProperties.GetProperty("nullProp").ValueKind.Should().Be(JsonValueKind.Null);
fakeAgent.ReceivedForwardedProperties.GetProperty("arrayProp").GetArrayLength().Should().Be(3);
fakeAgent.ReceivedForwardedProperties.GetProperty("objectProp").GetProperty("nested").GetString().Should().Be("value");
}
private async Task SetupTestServerAsync(FakeForwardedPropsAgent fakeAgent)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services.AddAGUI();
builder.WebHost.UseTestServer();
this._app = builder.Build();
this._app.MapAGUI("/agent", fakeAgent);
await this._app.StartAsync();
TestServer testServer = this._app.Services.GetRequiredService<IServer>() as TestServer
?? throw new InvalidOperationException("TestServer not found");
this._client = testServer.CreateClient();
}
public async ValueTask DisposeAsync()
{
this._client?.Dispose();
if (this._app != null)
{
await this._app.DisposeAsync();
}
}
}
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated in tests")]
internal sealed class FakeForwardedPropsAgent : AIAgent
{
public FakeForwardedPropsAgent()
{
}
public override string? Description => "Agent for forwarded properties testing";
public JsonElement ReceivedForwardedProperties { get; private set; }
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
return this.RunStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
}
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Extract forwarded properties from ChatOptions.AdditionalProperties (set by AG-UI hosting layer)
if (options is ChatClientAgentRunOptions { ChatOptions.AdditionalProperties: { } properties } &&
properties.TryGetValue("ag_ui_forwarded_properties", out object? propsObj) &&
propsObj is JsonElement forwardedProps)
{
this.ReceivedForwardedProperties = forwardedProps;
}
// Always return a text response
string messageId = Guid.NewGuid().ToString("N");
yield return new AgentRunResponseUpdate
{
MessageId = messageId,
Role = ChatRole.Assistant,
Contents = [new TextContent("Forwarded props processed")]
};
await Task.CompletedTask;
}
public override AgentThread GetNewThread() => new FakeInMemoryAgentThread();
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
{
return new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions);
}
private sealed class FakeInMemoryAgentThread : InMemoryAgentThread
{
public FakeInMemoryAgentThread()
: base()
{
}
public FakeInMemoryAgentThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
: base(serializedThread, jsonSerializerOptions)
{
}
}
public override object? GetService(Type serviceType, object? serviceKey = null) => null;
}
@@ -20,10 +20,6 @@
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
<PackageReference Include="System.Net.ServerSentEvents" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
@@ -9,8 +9,6 @@
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
</ItemGroup>
@@ -8,7 +8,6 @@
<ItemGroup>
<PackageReference Include="ModelContextProtocol" />
<PackageReference Include="System.Net.ServerSentEvents" />
</ItemGroup>
<ItemGroup>
@@ -92,7 +92,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
{
Name = "Test Agent",
Description = "Test description",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -223,7 +223,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
{
Name = "Override Name",
Description = "Override Description",
Instructions = "Override Instructions"
ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -250,7 +250,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
{
Name = "Override Name",
Description = "Override Description",
Instructions = "Override Instructions"
ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -299,7 +299,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
{
Name = "Override Name",
Description = "Override Description",
Instructions = "Override Instructions"
ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -326,7 +326,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
{
Name = "Override Name",
Description = "Override Description",
Instructions = "Override Instructions"
ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -498,7 +498,7 @@ public sealed class OpenAIAssistantClientExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "Test Agent",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -130,7 +130,7 @@ public sealed class OpenAIChatClientExtensionsTests
{
Name = "Test Agent",
Description = "Test description",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -208,7 +208,7 @@ public sealed class OpenAIResponseClientExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "Test Agent",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -19,7 +19,6 @@ public class ChatClientAgentOptionsTests
// Assert
Assert.Null(options.Name);
Assert.Null(options.Instructions);
Assert.Null(options.Description);
Assert.Null(options.ChatOptions);
Assert.Null(options.ChatMessageStoreFactory);
@@ -27,90 +26,44 @@ public class ChatClientAgentOptionsTests
}
[Fact]
public void ParameterizedConstructor_WithNullValues_SetsPropertiesCorrectly()
public void Constructor_WithNullValues_SetsPropertiesCorrectly()
{
// Act
var options = new ChatClientAgentOptions(
instructions: null,
name: null,
description: null,
tools: null);
var options = new ChatClientAgentOptions() { Name = null, Description = null, ChatOptions = new() { Tools = null, Instructions = null } };
// Assert
Assert.Null(options.Name);
Assert.Null(options.Instructions);
Assert.Null(options.Description);
Assert.Null(options.ChatOptions);
Assert.Null(options.AIContextProviderFactory);
Assert.Null(options.ChatMessageStoreFactory);
Assert.NotNull(options.ChatOptions);
Assert.Null(options.ChatOptions.Instructions);
Assert.Null(options.ChatOptions.Tools);
}
[Fact]
public void ParameterizedConstructor_WithInstructionsOnly_SetsChatOptionsWithInstructions()
{
// Arrange
const string Instructions = "Test instructions";
// Act
var options = new ChatClientAgentOptions(
instructions: Instructions,
name: null,
description: null,
tools: null);
// Assert
Assert.Null(options.Name);
Assert.Equal(Instructions, options.Instructions);
Assert.Null(options.Description);
Assert.Null(options.ChatOptions);
}
[Fact]
public void ParameterizedConstructor_WithToolsOnly_SetsChatOptionsWithTools()
public void Constructor_WithToolsOnly_SetsChatOptionsWithTools()
{
// Arrange
var tools = new List<AITool> { AIFunctionFactory.Create(() => "test") };
// Act
var options = new ChatClientAgentOptions(
instructions: null,
name: null,
description: null,
tools: tools);
var options = new ChatClientAgentOptions()
{
Name = null,
Description = null,
ChatOptions = new() { Tools = tools }
};
// Assert
Assert.Null(options.Name);
Assert.Null(options.Instructions);
Assert.Null(options.Description);
Assert.NotNull(options.ChatOptions);
Assert.Null(options.ChatOptions.Instructions);
Assert.Same(tools, options.ChatOptions.Tools);
AssertSameTools(tools, options.ChatOptions.Tools);
}
[Fact]
public void ParameterizedConstructor_WithInstructionsAndTools_SetsChatOptionsWithBoth()
{
// Arrange
const string Instructions = "Test instructions";
var tools = new List<AITool> { AIFunctionFactory.Create(() => "test") };
// Act
var options = new ChatClientAgentOptions(
instructions: Instructions,
name: null,
description: null,
tools: tools);
// Assert
Assert.Null(options.Name);
Assert.Equal(Instructions, options.Instructions);
Assert.Null(options.Description);
Assert.NotNull(options.ChatOptions);
Assert.Null(options.ChatOptions.Instructions);
Assert.Same(tools, options.ChatOptions.Tools);
}
[Fact]
public void ParameterizedConstructor_WithAllParameters_SetsAllPropertiesCorrectly()
public void Constructor_WithAllParameters_SetsAllPropertiesCorrectly()
{
// Arrange
const string Instructions = "Test instructions";
@@ -119,38 +72,37 @@ public class ChatClientAgentOptionsTests
var tools = new List<AITool> { AIFunctionFactory.Create(() => "test") };
// Act
var options = new ChatClientAgentOptions(
instructions: Instructions,
name: Name,
description: Description,
tools: tools);
var options = new ChatClientAgentOptions()
{
Name = Name,
Description = Description,
ChatOptions = new() { Tools = tools, Instructions = Instructions }
};
// Assert
Assert.Equal(Name, options.Name);
Assert.Equal(Instructions, options.Instructions);
Assert.Equal(Instructions, options.ChatOptions.Instructions);
Assert.Equal(Description, options.Description);
Assert.NotNull(options.ChatOptions);
Assert.Null(options.ChatOptions.Instructions);
Assert.Same(tools, options.ChatOptions.Tools);
AssertSameTools(tools, options.ChatOptions.Tools);
}
[Fact]
public void ParameterizedConstructor_WithNameAndDescriptionOnly_DoesNotCreateChatOptions()
public void Constructor_WithNameAndDescriptionOnly_DoesNotCreateChatOptions()
{
// Arrange
const string Name = "Test name";
const string Description = "Test description";
// Act
var options = new ChatClientAgentOptions(
instructions: null,
name: Name,
description: Description,
tools: null);
var options = new ChatClientAgentOptions()
{
Name = Name,
Description = Description,
};
// Assert
Assert.Equal(Name, options.Name);
Assert.Null(options.Instructions);
Assert.Equal(Description, options.Description);
Assert.Null(options.ChatOptions);
}
@@ -159,7 +111,6 @@ public class ChatClientAgentOptionsTests
public void Clone_CreatesDeepCopyWithSameValues()
{
// Arrange
const string Instructions = "Test instructions";
const string Name = "Test name";
const string Description = "Test description";
var tools = new List<AITool> { AIFunctionFactory.Create(() => "test") };
@@ -171,8 +122,11 @@ public class ChatClientAgentOptionsTests
ChatClientAgentOptions.AIContextProviderFactoryContext ctx) =>
new Mock<AIContextProvider>().Object;
var original = new ChatClientAgentOptions(Instructions, Name, Description, tools)
var original = new ChatClientAgentOptions()
{
Name = Name,
Description = Description,
ChatOptions = new() { Tools = tools },
Id = "test-id",
ChatMessageStoreFactory = ChatMessageStoreFactory,
AIContextProviderFactory = AIContextProviderFactory
@@ -185,7 +139,6 @@ public class ChatClientAgentOptionsTests
Assert.NotSame(original, clone);
Assert.Equal(original.Id, clone.Id);
Assert.Equal(original.Name, clone.Name);
Assert.Equal(original.Instructions, clone.Instructions);
Assert.Equal(original.Description, clone.Description);
Assert.Same(original.ChatMessageStoreFactory, clone.ChatMessageStoreFactory);
Assert.Same(original.AIContextProviderFactory, clone.AIContextProviderFactory);
@@ -197,14 +150,13 @@ public class ChatClientAgentOptionsTests
}
[Fact]
public void Clone_WithNullChatOptions_ClonesCorrectly()
public void Clone_WithoutProvidingChatOptions_ClonesCorrectly()
{
// Arrange
var original = new ChatClientAgentOptions
{
Id = "test-id",
Name = "Test name",
Instructions = "Test instructions",
Description = "Test description"
};
@@ -215,10 +167,19 @@ public class ChatClientAgentOptionsTests
Assert.NotSame(original, clone);
Assert.Equal(original.Id, clone.Id);
Assert.Equal(original.Name, clone.Name);
Assert.Equal(original.Instructions, clone.Instructions);
Assert.Equal(original.Description, clone.Description);
Assert.Null(clone.ChatOptions);
Assert.Null(original.ChatOptions);
Assert.Null(clone.ChatMessageStoreFactory);
Assert.Null(clone.AIContextProviderFactory);
}
private static void AssertSameTools(IList<AITool>? expected, IList<AITool>? actual)
{
var index = 0;
foreach (var tool in expected ?? [])
{
Assert.Same(tool, actual?[index]);
index++;
}
}
}
@@ -31,7 +31,7 @@ public partial class ChatClientAgentTests
Id = "test-agent-id",
Name = "test name",
Description = "test description",
Instructions = "test instructions",
ChatOptions = new() { Instructions = "test instructions" },
});
// Assert
@@ -65,7 +65,7 @@ public partial class ChatClientAgentTests
ChatClientAgent agent =
new(mockService.Object, options: new()
{
Instructions = "test instructions"
ChatOptions = new() { Instructions = "base instructions" },
});
// Act
@@ -99,7 +99,7 @@ public partial class ChatClientAgentTests
{
// Arrange
var chatClient = new Mock<IChatClient>().Object;
ChatClientAgent agent = new(chatClient, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(chatClient, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(() => agent.RunAsync((IReadOnlyCollection<ChatMessage>)null!));
@@ -120,7 +120,7 @@ public partial class ChatClientAgentTests
It.Is<ChatOptions>(opts => opts.MaxOutputTokens == 100),
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
// Act
await agent.RunAsync([new(ChatRole.User, "test")], options: new ChatClientAgentRunOptions(chatOptions));
@@ -181,7 +181,7 @@ public partial class ChatClientAgentTests
capturedMessages.AddRange(msgs))
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions" } });
var runOptions = new AgentRunOptions();
// Act
@@ -212,7 +212,7 @@ public partial class ChatClientAgentTests
It.IsAny<ChatOptions>(),
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse(responseMessages));
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions", Name = authorName });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, Name = authorName });
// Act
var result = await agent.RunAsync([new(ChatRole.User, "test")]);
@@ -239,7 +239,7 @@ public partial class ChatClientAgentTests
capturedMessages.AddRange(msgs))
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
// Create a thread using the agent's GetNewThread method
var thread = agent.GetNewThread();
@@ -270,7 +270,7 @@ public partial class ChatClientAgentTests
capturedMessages.AddRange(msgs))
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = null });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = null } });
// Act
await agent.RunAsync([new(ChatRole.User, "test message")]);
@@ -300,7 +300,7 @@ public partial class ChatClientAgentTests
capturedMessages.AddRange(msgs))
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
// Act
await agent.RunAsync([]);
@@ -326,7 +326,7 @@ public partial class ChatClientAgentTests
It.Is<ChatOptions>(opts => opts.ConversationId == "ConvId"),
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" });
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
ChatClientAgentThread thread = new() { ConversationId = "ConvId" };
@@ -346,7 +346,7 @@ public partial class ChatClientAgentTests
var chatOptions = new ChatOptions { ConversationId = "ConvId" };
Mock<IChatClient> mockService = new();
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
ChatClientAgentThread thread = new() { ConversationId = "ThreadId" };
@@ -369,7 +369,7 @@ public partial class ChatClientAgentTests
It.Is<ChatOptions>(opts => opts.MaxOutputTokens == 100 && opts.ConversationId == "ConvId"),
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" });
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
ChatClientAgentThread thread = new() { ConversationId = "ConvId" };
@@ -394,7 +394,7 @@ public partial class ChatClientAgentTests
It.IsAny<ChatOptions>(),
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
ChatClientAgentThread thread = new() { ConversationId = "ConvId" };
@@ -415,7 +415,7 @@ public partial class ChatClientAgentTests
It.IsAny<IEnumerable<ChatMessage>>(),
It.IsAny<ChatOptions>(),
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" });
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
ChatClientAgentThread thread = new();
// Act
@@ -442,7 +442,7 @@ public partial class ChatClientAgentTests
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>())).Returns(new InMemoryChatMessageStore());
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = new() { Instructions = "test instructions" },
ChatMessageStoreFactory = mockFactory.Object
});
@@ -473,7 +473,7 @@ public partial class ChatClientAgentTests
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = new() { Instructions = "test instructions" },
});
// Act
@@ -508,7 +508,7 @@ public partial class ChatClientAgentTests
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = new() { Instructions = "test instructions" },
ChatMessageStoreFactory = mockFactory.Object
});
@@ -539,7 +539,7 @@ public partial class ChatClientAgentTests
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>())).Returns(new InMemoryChatMessageStore());
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = new() { Instructions = "test instructions" },
ChatMessageStoreFactory = mockFactory.Object
});
@@ -592,7 +592,7 @@ public partial class ChatClientAgentTests
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
.Returns(new ValueTask());
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
// Act
var thread = agent.GetNewThread() as ChatClientAgentThread;
@@ -654,7 +654,7 @@ public partial class ChatClientAgentTests
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
.Returns(new ValueTask());
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
// Act
await Assert.ThrowsAsync<InvalidOperationException>(() => agent.RunAsync(requestMessages));
@@ -700,7 +700,7 @@ public partial class ChatClientAgentTests
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new AIContext());
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
// Act
await agent.RunAsync([new(ChatRole.User, "user message")]);
@@ -907,7 +907,7 @@ public partial class ChatClientAgentTests
{
// Arrange
var chatClient = new Mock<IChatClient>().Object;
var metadata = new ChatClientAgentOptions { Instructions = "You are a helpful assistant" };
var metadata = new ChatClientAgentOptions { ChatOptions = new() { Instructions = "You are a helpful assistant" } };
ChatClientAgent agent = new(chatClient, metadata);
// Act & Assert
@@ -936,7 +936,7 @@ public partial class ChatClientAgentTests
{
// Arrange
var chatClient = new Mock<IChatClient>().Object;
var metadata = new ChatClientAgentOptions { Instructions = null };
var metadata = new ChatClientAgentOptions { ChatOptions = new() { Instructions = null } };
ChatClientAgent agent = new(chatClient, metadata);
// Act & Assert
@@ -967,10 +967,10 @@ public partial class ChatClientAgentTests
}
/// <summary>
/// Verify that ChatOptions property returns null when no params are provided that require a ChatOptions instance.
/// Verify that ChatOptions is created with instructions when instructions are provided and no tools are provided.
/// </summary>
[Fact]
public void ChatOptionsReturnsNullWhenConstructorToolsNotProvided()
public void ChatOptionsCreatedWithInstructionsEvenWhenConstructorToolsNotProvided()
{
// Arrange
var chatClient = new Mock<IChatClient>().Object;
@@ -980,7 +980,8 @@ public partial class ChatClientAgentTests
Assert.Equal("TestInstructions", agent.Instructions);
Assert.Equal("TestName", agent.Name);
Assert.Equal("TestDescription", agent.Description);
Assert.Null(agent.ChatOptions);
Assert.NotNull(agent.ChatOptions);
Assert.Equal("TestInstructions", agent.ChatOptions.Instructions);
}
#endregion
@@ -1071,7 +1072,7 @@ public partial class ChatClientAgentTests
public async Task ChatOptionsMergingUsesAgentOptionsWhenRequestHasNoneAsync()
{
// Arrange
var agentChatOptions = new ChatOptions { MaxOutputTokens = 100, Temperature = 0.7f };
var agentChatOptions = new ChatOptions { MaxOutputTokens = 100, Temperature = 0.7f, Instructions = "test instructions" };
Mock<IChatClient> mockService = new();
ChatOptions? capturedChatOptions = null;
mockService.Setup(
@@ -1085,7 +1086,6 @@ public partial class ChatClientAgentTests
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = agentChatOptions
});
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
@@ -1114,7 +1114,7 @@ public partial class ChatClientAgentTests
capturedChatOptions = opts)
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
ChatClientAgent agent = new(mockService.Object, options: new("test instructions"));
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
// Act
@@ -1167,6 +1167,7 @@ public partial class ChatClientAgentTests
// Arrange
var agentChatOptions = new ChatOptions
{
Instructions = "test instructions",
MaxOutputTokens = 100,
Temperature = 0.7f,
TopP = 0.9f,
@@ -1204,7 +1205,6 @@ public partial class ChatClientAgentTests
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = agentChatOptions
});
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
@@ -1263,6 +1263,7 @@ public partial class ChatClientAgentTests
var agentChatOptions = new ChatOptions
{
Instructions = "test instructions",
Tools = [agentTool]
};
var requestChatOptions = new ChatOptions
@@ -1283,7 +1284,6 @@ public partial class ChatClientAgentTests
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = agentChatOptions
});
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
@@ -1312,6 +1312,7 @@ public partial class ChatClientAgentTests
var agentChatOptions = new ChatOptions
{
Instructions = "test instructions",
Tools = [agentTool]
};
var requestChatOptions = new ChatOptions
@@ -1333,7 +1334,6 @@ public partial class ChatClientAgentTests
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = agentChatOptions
});
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
@@ -1360,6 +1360,7 @@ public partial class ChatClientAgentTests
// Arrange
var agentChatOptions = new ChatOptions
{
Instructions = "test instructions",
RawRepresentationFactory = _ => agentSetting
};
var requestChatOptions = new ChatOptions
@@ -1380,7 +1381,6 @@ public partial class ChatClientAgentTests
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = agentChatOptions
});
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
@@ -1436,7 +1436,7 @@ public partial class ChatClientAgentTests
TopK = 50,
PresencePenalty = 0.1f,
FrequencyPenalty = 0.2f,
Instructions = "test instructions\nrequest instructions",
Instructions = "agent instructions\nrequest instructions",
ModelId = "agent-model",
Seed = 12345,
ConversationId = "agent-conversation",
@@ -1459,7 +1459,6 @@ public partial class ChatClientAgentTests
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = agentChatOptions
});
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
@@ -1509,7 +1508,7 @@ public partial class ChatClientAgentTests
{
Id = "test-agent-id",
Name = "TestAgent",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1532,7 +1531,7 @@ public partial class ChatClientAgentTests
var mockChatClient = new Mock<IChatClient>();
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1556,7 +1555,7 @@ public partial class ChatClientAgentTests
var mockChatClient = new Mock<IChatClient>();
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1582,7 +1581,7 @@ public partial class ChatClientAgentTests
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1606,7 +1605,7 @@ public partial class ChatClientAgentTests
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1632,7 +1631,7 @@ public partial class ChatClientAgentTests
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1661,7 +1660,7 @@ public partial class ChatClientAgentTests
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1694,7 +1693,7 @@ public partial class ChatClientAgentTests
{
Id = "test-agent-id",
Name = "TestAgent",
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1721,7 +1720,7 @@ public partial class ChatClientAgentTests
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1756,12 +1755,12 @@ public partial class ChatClientAgentTests
var chatClientAgent1 = new ChatClientAgent(mockChatClient1.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions 1"
ChatOptions = new() { Instructions = "Test instructions 1" }
});
var chatClientAgent2 = new ChatClientAgent(mockChatClient2.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions 2"
ChatOptions = new() { Instructions = "Test instructions 2" }
});
// Act
@@ -1796,7 +1795,7 @@ public partial class ChatClientAgentTests
var mockChatClient = new Mock<IChatClient>();
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1820,7 +1819,7 @@ public partial class ChatClientAgentTests
var mockChatClient = new Mock<IChatClient>();
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act
@@ -1845,7 +1844,7 @@ public partial class ChatClientAgentTests
var mockChatClient = new Mock<IChatClient>();
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act - Request IChatClient with a service key (base.GetService will return null due to serviceKey)
@@ -1870,7 +1869,7 @@ public partial class ChatClientAgentTests
mockChatClient.Setup(c => c.GetService(typeof(string), "some-key")).Returns("test-result");
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions"
ChatOptions = new() { Instructions = "Test instructions" }
});
// Act - Request string with a service key (base.GetService will return null due to serviceKey)
@@ -1911,7 +1910,7 @@ public partial class ChatClientAgentTests
ChatClientAgent agent =
new(mockService.Object, options: new()
{
Instructions = "test instructions"
ChatOptions = new() { Instructions = "test instructions" }
});
// Act
@@ -1958,7 +1957,7 @@ public partial class ChatClientAgentTests
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>())).Returns(new InMemoryChatMessageStore());
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = new() { Instructions = "test instructions" },
ChatMessageStoreFactory = mockFactory.Object
});
@@ -1996,7 +1995,7 @@ public partial class ChatClientAgentTests
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>())).Returns(new InMemoryChatMessageStore());
ChatClientAgent agent = new(mockService.Object, options: new()
{
Instructions = "test instructions",
ChatOptions = new() { Instructions = "test instructions" },
ChatMessageStoreFactory = mockFactory.Object
});
@@ -2049,7 +2048,7 @@ public partial class ChatClientAgentTests
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
.Returns(new ValueTask());
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object });
// Act
var thread = agent.GetNewThread() as ChatClientAgentThread;
@@ -2112,7 +2111,7 @@ public partial class ChatClientAgentTests
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
.Returns(new ValueTask());
ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object });
// Act
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
@@ -20,7 +20,7 @@ public class ChatClientAgent_DeserializeThreadTests
var factoryCalled = false;
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions",
ChatOptions = new() { Instructions = "Test instructions" },
AIContextProviderFactory = _ =>
{
factoryCalled = true;
@@ -53,7 +53,7 @@ public class ChatClientAgent_DeserializeThreadTests
var factoryCalled = false;
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions",
ChatOptions = new() { Instructions = "Test instructions" },
ChatMessageStoreFactory = _ =>
{
factoryCalled = true;
@@ -19,7 +19,7 @@ public class ChatClientAgent_GetNewThreadTests
var factoryCalled = false;
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions",
ChatOptions = new() { Instructions = "Test instructions" },
AIContextProviderFactory = _ =>
{
factoryCalled = true;
@@ -46,7 +46,7 @@ public class ChatClientAgent_GetNewThreadTests
var factoryCalled = false;
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
{
Instructions = "Test instructions",
ChatOptions = new() { Instructions = "Test instructions" },
ChatMessageStoreFactory = _ =>
{
factoryCalled = true;
@@ -90,7 +90,7 @@ public sealed class ChatClientBuilderExtensionsTests
{
Name = "AgentWithOptions",
Description = "Desc",
Instructions = "Instr",
ChatOptions = new() { Instructions = "Instr" },
UseProvidedChatClientAsIs = true
};
@@ -115,7 +115,7 @@ public sealed class ChatClientBuilderExtensionsTests
var options = new ChatClientAgentOptions
{
Name = "ServiceAgent",
Instructions = "Service instructions"
ChatOptions = new() { Instructions = "Service instructions" }
};
// Act
@@ -148,7 +148,7 @@ public sealed class ChatClientBuilderExtensionsTests
ChatClientBuilder builder = null!;
// Act & Assert
Assert.Throws<ArgumentNullException>(() => builder.BuildAIAgent(options: new() { Instructions = "instructions" }));
Assert.Throws<ArgumentNullException>(() => builder.BuildAIAgent(options: new() { ChatOptions = new() { Instructions = "instructions" } }));
}
[Fact]
@@ -166,7 +166,7 @@ public sealed class ChatClientBuilderExtensionsTests
var agent = builder.BuildAIAgent(
new ChatClientAgentOptions
{
Instructions = "Middleware test",
ChatOptions = new() { Instructions = "Middleware test" },
UseProvidedChatClientAsIs = true
}
);
@@ -57,7 +57,7 @@ public sealed class ChatClientExtensionsTests
{
Name = "AgentWithOptions",
Description = "Desc",
Instructions = "Instr",
ChatOptions = new() { Instructions = "Instr" },
UseProvidedChatClientAsIs = true
};
@@ -89,6 +89,6 @@ public sealed class ChatClientExtensionsTests
IChatClient chatClient = null!;
// Act & Assert
Assert.Throws<ArgumentNullException>(() => chatClient.CreateAIAgent(options: new() { Instructions = "instructions" }));
Assert.Throws<ArgumentNullException>(() => chatClient.CreateAIAgent(options: new() { ChatOptions = new() { Instructions = "instructions" } }));
}
}
@@ -6,7 +6,6 @@ using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Data;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Moq;
@@ -40,6 +40,12 @@ internal sealed class MockAgentProvider : Mock<WorkflowAgentProvider>
It.IsAny<bool>(),
It.IsAny<CancellationToken>()))
.Returns(ToAsyncEnumerableAsync(testMessages));
this.Setup(provider => provider.CreateMessageAsync(
It.IsAny<string>(),
It.IsAny<ChatMessage>(),
It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(testMessages.First()));
}
private string CreateConversationId()
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
using Microsoft.Bot.ObjectModel;
using Microsoft.Extensions.AI;
using Xunit.Abstractions;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.ObjectModel;
/// <summary>
/// Tests for <see cref="AddConversationMessageExecutor"/>.
/// </summary>
public sealed class AddConversationMessageExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
{
[Theory]
[InlineData(AgentMessageRole.User)]
[InlineData(AgentMessageRole.Agent)]
public async Task AddMessageSuccessfullyAsync(AgentMessageRole role)
{
// Arrange, Act, Assert
await this.ExecuteTestAsync(
displayName: nameof(AddMessageSuccessfullyAsync),
variableName: "TestMessage",
role: AgentMessageRoleWrapper.Get(role),
messageText: $"Hello from {role}");
}
private async Task ExecuteTestAsync(
string displayName,
string variableName,
AgentMessageRoleWrapper role,
string messageText)
{
// Arrange
MockAgentProvider mockAgentProvider = new();
AddConversationMessage model = this.CreateModel(
this.FormatDisplayName(displayName),
FormatVariablePath(variableName),
"TestConversationId",
role,
messageText);
AddConversationMessageExecutor action = new(model, mockAgentProvider.Object, this.State);
// Act
await this.ExecuteAsync(action);
// Assert
ChatMessage? testMessage = mockAgentProvider.TestMessages?.FirstOrDefault();
Assert.NotNull(testMessage);
VerifyModel(model, action);
this.VerifyState(variableName, testMessage.ToRecord());
}
private AddConversationMessage CreateModel(
string displayName,
string messageVariable,
string conversationId,
AgentMessageRoleWrapper role,
string messageText)
{
AddConversationMessage.Builder actionBuilder =
new()
{
Id = this.CreateActionId(),
DisplayName = this.FormatDisplayName(displayName),
Message = PropertyPath.Create(messageVariable),
ConversationId = StringExpression.Literal(conversationId),
Role = role,
};
actionBuilder.Content.Add(new AddConversationMessageContent.Builder
{
Type = AgentMessageContentType.Text,
Value = TemplateLine.Parse(messageText)
});
return AssignParent<AddConversationMessage>(actionBuilder);
}
}
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests.Support;
@@ -37,14 +38,24 @@ public class OpenAIAssistantClientExtensionsTests
{
"CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync(
model: s_config.ChatModelId!,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
tools: [weatherFunction])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = AgentInstructions,
Tools = [weatherFunction]
}
}),
"CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent(
model: s_config.ChatModelId!,
options: new ChatClientAgentOptions(
instructions: AgentInstructions,
tools: [weatherFunction])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = AgentInstructions,
Tools = [weatherFunction]
}
}),
"CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync(
model: s_config.ChatModelId!,
instructions: AgentInstructions,
@@ -94,14 +105,24 @@ public class OpenAIAssistantClientExtensionsTests
{
"CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync(
model: s_config.ChatModelId!,
options: new ChatClientAgentOptions(
instructions: Instructions,
tools: [codeInterpreterTool])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = Instructions,
Tools = [codeInterpreterTool]
}
}),
"CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent(
model: s_config.ChatModelId!,
options: new ChatClientAgentOptions(
instructions: Instructions,
tools: [codeInterpreterTool])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = Instructions,
Tools = [codeInterpreterTool]
}
}),
"CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync(
model: s_config.ChatModelId!,
instructions: Instructions,
@@ -153,20 +174,33 @@ public class OpenAIAssistantClientExtensionsTests
});
string vectorStoreId = vectorStoreCreate.Value.Id;
// Wait for vector store indexing to complete before using it
await WaitForVectorStoreReadyAsync(vectorStoreClient, vectorStoreId);
var fileSearchTool = new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreId)] };
var agent = createMechanism switch
{
"CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync(
model: s_config.ChatModelId!,
options: new ChatClientAgentOptions(
instructions: Instructions,
tools: [fileSearchTool])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = Instructions,
Tools = [fileSearchTool]
}
}),
"CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent(
model: s_config.ChatModelId!,
options: new ChatClientAgentOptions(
instructions: Instructions,
tools: [fileSearchTool])),
options: new ChatClientAgentOptions()
{
ChatOptions = new()
{
Instructions = Instructions,
Tools = [fileSearchTool]
}
}),
"CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync(
model: s_config.ChatModelId!,
instructions: Instructions,
@@ -189,4 +223,43 @@ public class OpenAIAssistantClientExtensionsTests
File.Delete(searchFilePath);
}
}
/// <summary>
/// Waits for a vector store to complete indexing by polling its status.
/// </summary>
/// <param name="client">The vector store client.</param>
/// <param name="vectorStoreId">The ID of the vector store.</param>
/// <param name="maxWaitSeconds">Maximum time to wait in seconds (default: 30).</param>
/// <returns>A task that completes when the vector store is ready or throws on timeout/failure.</returns>
private static async Task WaitForVectorStoreReadyAsync(
VectorStoreClient client,
string vectorStoreId,
int maxWaitSeconds = 30)
{
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < maxWaitSeconds)
{
VectorStore vectorStore = await client.GetVectorStoreAsync(vectorStoreId);
VectorStoreStatus status = vectorStore.Status;
if (status == VectorStoreStatus.Completed)
{
if (vectorStore.FileCounts.Failed > 0)
{
throw new InvalidOperationException("Vector store indexing failed for some files");
}
return;
}
if (status == VectorStoreStatus.Expired)
{
throw new InvalidOperationException("Vector store has expired");
}
await Task.Delay(1000);
}
throw new TimeoutException($"Vector store did not complete indexing within {maxWaitSeconds}s");
}
}
@@ -47,8 +47,7 @@ public class OpenAIChatCompletionFixture : IChatClientAgentFixture
return Task.FromResult(new ChatClientAgent(chatClient, options: new()
{
Name = name,
Instructions = instructions,
ChatOptions = new() { Tools = aiTools }
ChatOptions = new() { Instructions = instructions, Tools = aiTools }
}));
}
@@ -73,9 +73,9 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
options: new()
{
Name = name,
Instructions = instructions,
ChatOptions = new ChatOptions
{
Instructions = instructions,
Tools = aiTools,
RawRepresentationFactory = new Func<IChatClient, object>(_ => new ResponseCreationOptions() { StoredOutputEnabled = store })
},