mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-08 20:58:45 +02:00
fix: preserve single-chat final messages
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -146,7 +146,7 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
else if (evt is WorkflowOutputEvent outputEvent)
|
else if (evt is WorkflowOutputEvent outputEvent)
|
||||||
{
|
{
|
||||||
List<ChatMessage> allMessages = outputEvent.As<List<ChatMessage>>() ?? [];
|
List<ChatMessage> allMessages = outputEvent.As<List<ChatMessage>>() ?? [];
|
||||||
List<ChatMessage> newMessages = allMessages.Skip(inputMessages.Count).ToList();
|
List<ChatMessage> newMessages = SelectNewOutputMessages(allMessages, inputMessages);
|
||||||
completedMessages = ProjectCompletedMessages(
|
completedMessages = ProjectCompletedMessages(
|
||||||
command,
|
command,
|
||||||
newMessages,
|
newMessages,
|
||||||
@@ -369,6 +369,70 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static List<ChatMessage> SelectNewOutputMessages(
|
||||||
|
IReadOnlyList<ChatMessage> outputMessages,
|
||||||
|
IReadOnlyList<ChatMessage> inputMessages)
|
||||||
|
{
|
||||||
|
if (outputMessages.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputMessages.Count == 0)
|
||||||
|
{
|
||||||
|
return outputMessages.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
int overlapLength = FindOutputInputOverlapLength(outputMessages, inputMessages);
|
||||||
|
return outputMessages.Skip(overlapLength).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int FindOutputInputOverlapLength(
|
||||||
|
IReadOnlyList<ChatMessage> outputMessages,
|
||||||
|
IReadOnlyList<ChatMessage> inputMessages)
|
||||||
|
{
|
||||||
|
int maxOverlap = Math.Min(outputMessages.Count, inputMessages.Count);
|
||||||
|
|
||||||
|
for (int overlapLength = maxOverlap; overlapLength > 0; overlapLength--)
|
||||||
|
{
|
||||||
|
int inputStart = inputMessages.Count - overlapLength;
|
||||||
|
bool matches = true;
|
||||||
|
|
||||||
|
for (int index = 0; index < overlapLength; index++)
|
||||||
|
{
|
||||||
|
if (!ChatMessagesMatch(inputMessages[inputStart + index], outputMessages[index]))
|
||||||
|
{
|
||||||
|
matches = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches)
|
||||||
|
{
|
||||||
|
return overlapLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ChatMessagesMatch(ChatMessage inputMessage, ChatMessage outputMessage)
|
||||||
|
{
|
||||||
|
if (inputMessage.Role != outputMessage.Role)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(inputMessage.Text, outputMessage.Text, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(inputMessage.AuthorName)
|
||||||
|
|| string.IsNullOrWhiteSpace(outputMessage.AuthorName)
|
||||||
|
|| string.Equals(inputMessage.AuthorName, outputMessage.AuthorName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private static string ResolveProjectedAuthorName(
|
private static string ResolveProjectedAuthorName(
|
||||||
PatternDefinitionDto pattern,
|
PatternDefinitionDto pattern,
|
||||||
string? primaryIdentifier,
|
string? primaryIdentifier,
|
||||||
|
|||||||
@@ -6,6 +6,72 @@ namespace Kopaya.AgentHost.Tests;
|
|||||||
|
|
||||||
public sealed class CopilotWorkflowRunnerTests
|
public sealed class CopilotWorkflowRunnerTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SelectNewOutputMessages_SkipsFullTranscriptPrefix()
|
||||||
|
{
|
||||||
|
List<ChatMessage> inputMessages =
|
||||||
|
[
|
||||||
|
new(ChatRole.User, "Hello"),
|
||||||
|
];
|
||||||
|
List<ChatMessage> outputMessages =
|
||||||
|
[
|
||||||
|
new(ChatRole.User, "Hello"),
|
||||||
|
new(ChatRole.Assistant, "Hi there."),
|
||||||
|
];
|
||||||
|
|
||||||
|
IReadOnlyList<ChatMessage> newMessages = CopilotWorkflowRunner.SelectNewOutputMessages(
|
||||||
|
outputMessages,
|
||||||
|
inputMessages);
|
||||||
|
|
||||||
|
ChatMessage message = Assert.Single(newMessages);
|
||||||
|
Assert.Equal(ChatRole.Assistant, message.Role);
|
||||||
|
Assert.Equal("Hi there.", message.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SelectNewOutputMessages_SkipsOnlyTheLatestInputOverlap()
|
||||||
|
{
|
||||||
|
List<ChatMessage> inputMessages =
|
||||||
|
[
|
||||||
|
new(ChatRole.Assistant, "Earlier answer"),
|
||||||
|
new(ChatRole.User, "Hello"),
|
||||||
|
];
|
||||||
|
List<ChatMessage> outputMessages =
|
||||||
|
[
|
||||||
|
new(ChatRole.User, "Hello"),
|
||||||
|
new(ChatRole.Assistant, "Hi there."),
|
||||||
|
];
|
||||||
|
|
||||||
|
IReadOnlyList<ChatMessage> newMessages = CopilotWorkflowRunner.SelectNewOutputMessages(
|
||||||
|
outputMessages,
|
||||||
|
inputMessages);
|
||||||
|
|
||||||
|
ChatMessage message = Assert.Single(newMessages);
|
||||||
|
Assert.Equal(ChatRole.Assistant, message.Role);
|
||||||
|
Assert.Equal("Hi there.", message.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SelectNewOutputMessages_PreservesAssistantOnlyOutput()
|
||||||
|
{
|
||||||
|
List<ChatMessage> inputMessages =
|
||||||
|
[
|
||||||
|
new(ChatRole.User, "Hello"),
|
||||||
|
];
|
||||||
|
List<ChatMessage> outputMessages =
|
||||||
|
[
|
||||||
|
new(ChatRole.Assistant, "Hi there."),
|
||||||
|
];
|
||||||
|
|
||||||
|
IReadOnlyList<ChatMessage> newMessages = CopilotWorkflowRunner.SelectNewOutputMessages(
|
||||||
|
outputMessages,
|
||||||
|
inputMessages);
|
||||||
|
|
||||||
|
ChatMessage message = Assert.Single(newMessages);
|
||||||
|
Assert.Equal(ChatRole.Assistant, message.Role);
|
||||||
|
Assert.Equal("Hi there.", message.Text);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ProjectCompletedMessages_FallsBackToStreamingSegmentsWhenWorkflowOutputIsMissing()
|
public void ProjectCompletedMessages_FallsBackToStreamingSegmentsWhenWorkflowOutputIsMissing()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user