Skip to content

Commit 268f3b7

Browse files
committed
test(core): stabilize parallel tool completion ordering
1 parent d93264b commit 268f3b7

1 file changed

Lines changed: 33 additions & 5 deletions

File tree

tests/DotCraft.Core.Tests/ChatClient/ToolCalling/StreamingFunctionInvokingChatClientTests.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using DotCraft.Context;
44
using DotCraft.Context.Compaction;
55
using DotCraft.Hooks;
6+
using System.Collections.Concurrent;
67
using System.ClientModel.Primitives;
78
using System.Text.Json;
89
using Microsoft.Extensions.AI;
@@ -1030,6 +1031,11 @@ public async Task GetStreamingResponseAsync_ThrowsContextOverflow_WhenErrorConte
10301031
public async Task GetStreamingResponseAsync_EmitsToolExecutionCompletionAsEachParallelToolFinishes()
10311032
{
10321033
var inner = new ParallelToolsFakeChatClient();
1034+
var slowStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
1035+
var fastStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
1036+
var releaseSlow = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
1037+
var releaseFast = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
1038+
var fastCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
10331039
var client = new StreamingFunctionInvokingChatClient(inner)
10341040
{
10351041
AllowConcurrentInvocation = true,
@@ -1042,16 +1048,18 @@ public async Task GetStreamingResponseAsync_EmitsToolExecutionCompletionAsEachPa
10421048
{
10431049
if (context.CallContent.CallId == "call-slow")
10441050
{
1045-
await Task.Delay(100);
1051+
slowStarted.TrySetResult();
1052+
await releaseSlow.Task;
10461053
return "slow result";
10471054
}
10481055

1049-
await Task.Delay(10);
1056+
fastStarted.TrySetResult();
1057+
await releaseFast.Task;
10501058
return "fast result";
10511059
}
10521060
};
10531061
var turn = new SessionTurn { Id = "turn_1", ThreadId = "thread_1", StartedAt = DateTimeOffset.UtcNow };
1054-
var completed = new List<SessionItem>();
1062+
var completed = new ConcurrentQueue<SessionItem>();
10551063

10561064
using var scope = ToolExecutionRuntimeScope.Set(new ToolExecutionRuntimeContext
10571065
{
@@ -1060,16 +1068,36 @@ public async Task GetStreamingResponseAsync_EmitsToolExecutionCompletionAsEachPa
10601068
Turn = turn,
10611069
NextItemSequence = () => turn.Items.Count + 1,
10621070
EmitItemStarted = _ => { },
1063-
EmitItemCompleted = item => completed.Add(item),
1071+
EmitItemCompleted = item =>
1072+
{
1073+
completed.Enqueue(item);
1074+
if (item.Payload is ToolExecutionPayload { CallId: "call-fast" })
1075+
fastCompleted.TrySetResult();
1076+
},
10641077
SupportsToolExecutionLifecycle = true
10651078
});
10661079
RegisterToolExecution(turn, "item_slow", "call-slow", "Slow");
10671080
RegisterToolExecution(turn, "item_fast", "call-fast", "Fast");
10681081

1069-
await foreach (var _ in client.GetStreamingResponseAsync([new ChatMessage(ChatRole.User, "start")]))
1082+
var execution = CollectAsync(client.GetStreamingResponseAsync([new ChatMessage(ChatRole.User, "start")]));
1083+
IReadOnlyList<string> completedBeforeSlowRelease;
1084+
try
1085+
{
1086+
await Task.WhenAll(slowStarted.Task, fastStarted.Task).WaitAsync(TimeSpan.FromSeconds(10));
1087+
releaseFast.TrySetResult();
1088+
await fastCompleted.Task.WaitAsync(TimeSpan.FromSeconds(10));
1089+
completedBeforeSlowRelease = completed
1090+
.Select(item => Assert.IsType<ToolExecutionPayload>(item.Payload).CallId)
1091+
.ToList();
1092+
}
1093+
finally
10701094
{
1095+
releaseFast.TrySetResult();
1096+
releaseSlow.TrySetResult();
10711097
}
10721098

1099+
await execution.WaitAsync(TimeSpan.FromSeconds(10));
1100+
Assert.Equal(["call-fast"], completedBeforeSlowRelease);
10731101
Assert.Equal(["call-fast", "call-slow"],
10741102
completed.Select(item => Assert.IsType<ToolExecutionPayload>(item.Payload).CallId));
10751103
Assert.Equal(2, inner.Calls.Count);

0 commit comments

Comments
 (0)