mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-27 13:23:57 +02:00
fix: make handoff filtering explicit
Explicitly configure handoff workflows to use HandoffToolCallFilteringBehavior.HandoffOnly instead of relying on Agent Framework's current default. This keeps normal tool-call history visible across handoffs while still stripping handoff plumbing, and adds a regression test to pin that behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -222,8 +222,7 @@ internal sealed class CopilotAgentBundle : IAsyncDisposable
|
|||||||
: pattern.Agents.FirstOrDefault()?.Id ?? topology.EntryAgentId;
|
: pattern.Agents.FirstOrDefault()?.Id ?? topology.EntryAgentId;
|
||||||
AIAgent entryAgent = agentMap.GetValueOrDefault(entryAgentId) ?? Agents[0];
|
AIAgent entryAgent = agentMap.GetValueOrDefault(entryAgentId) ?? Agents[0];
|
||||||
|
|
||||||
HandoffsWorkflowBuilder builder = AgentWorkflowBuilder.CreateHandoffBuilderWith(entryAgent)
|
HandoffsWorkflowBuilder builder = CreateHandoffWorkflowBuilder(entryAgent);
|
||||||
.WithHandoffInstructions(HandoffWorkflowGuidance.CreateWorkflowInstructions());
|
|
||||||
|
|
||||||
foreach (PatternHandoffRoute route in topology.Routes)
|
foreach (PatternHandoffRoute route in topology.Routes)
|
||||||
{
|
{
|
||||||
@@ -250,6 +249,16 @@ internal sealed class CopilotAgentBundle : IAsyncDisposable
|
|||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static HandoffsWorkflowBuilder CreateHandoffWorkflowBuilder(AIAgent entryAgent)
|
||||||
|
{
|
||||||
|
return AgentWorkflowBuilder.CreateHandoffBuilderWith(entryAgent)
|
||||||
|
// Preserve normal tool-call history across handoffs while still hiding the
|
||||||
|
// workflow's handoff plumbing. Make this explicit so AF default changes
|
||||||
|
// cannot silently alter Aryx handoff behavior.
|
||||||
|
.WithToolCallFilteringBehavior(HandoffToolCallFilteringBehavior.HandoffOnly)
|
||||||
|
.WithHandoffInstructions(HandoffWorkflowGuidance.CreateWorkflowInstructions());
|
||||||
|
}
|
||||||
|
|
||||||
private Workflow BuildGroupChatWorkflow(PatternDefinitionDto pattern)
|
private Workflow BuildGroupChatWorkflow(PatternDefinitionDto pattern)
|
||||||
{
|
{
|
||||||
int maximumIterations = pattern.MaxIterations <= 0 ? 5 : pattern.MaxIterations;
|
int maximumIterations = pattern.MaxIterations <= 0 ? 5 : pattern.MaxIterations;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Aryx.AgentHost.Contracts;
|
|||||||
using GitHub.Copilot.SDK;
|
using GitHub.Copilot.SDK;
|
||||||
using Aryx.AgentHost.Services;
|
using Aryx.AgentHost.Services;
|
||||||
using Microsoft.Agents.AI;
|
using Microsoft.Agents.AI;
|
||||||
|
using Microsoft.Agents.AI.Workflows;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
|
||||||
namespace Aryx.AgentHost.Tests;
|
namespace Aryx.AgentHost.Tests;
|
||||||
@@ -111,6 +112,24 @@ public sealed class CopilotAgentBundleTests
|
|||||||
Assert.Throws<NotSupportedException>(() => AryxCopilotAgent.CreateConfiguredSessionConfig(new SessionConfig(), options));
|
Assert.Throws<NotSupportedException>(() => AryxCopilotAgent.CreateConfiguredSessionConfig(new SessionConfig(), options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateHandoffWorkflowBuilder_ExplicitlyUsesHandoffOnlyFiltering()
|
||||||
|
{
|
||||||
|
ChatClientAgent entryAgent = CreateChatClientAgent("agent-1", "Primary");
|
||||||
|
|
||||||
|
HandoffsWorkflowBuilder builder = CopilotAgentBundle.CreateHandoffWorkflowBuilder(entryAgent);
|
||||||
|
|
||||||
|
FieldInfo field = typeof(HandoffsWorkflowBuilder).GetField(
|
||||||
|
"_toolCallFilteringBehavior",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
|
?? throw new InvalidOperationException("Expected HandoffsWorkflowBuilder to expose a filtering field.");
|
||||||
|
|
||||||
|
HandoffToolCallFilteringBehavior behavior = Assert.IsType<HandoffToolCallFilteringBehavior>(field.GetValue(builder));
|
||||||
|
|
||||||
|
Assert.Equal(HandoffToolCallFilteringBehavior.HandoffOnly, behavior);
|
||||||
|
Assert.Equal(HandoffWorkflowGuidance.CreateWorkflowInstructions(), builder.HandoffInstructions);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ConvertToolRequestsToFunctionCalls_MapsCallIdsNamesAndArguments()
|
public void ConvertToolRequestsToFunctionCalls_MapsCallIdsNamesAndArguments()
|
||||||
{
|
{
|
||||||
@@ -369,8 +388,48 @@ public sealed class CopilotAgentBundleTests
|
|||||||
CreateTool().JsonSchema);
|
CreateTool().JsonSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ChatClientAgent CreateChatClientAgent(string id, string name)
|
||||||
|
{
|
||||||
|
return new ChatClientAgent(
|
||||||
|
new StubChatClient(),
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
"Stub agent for handoff builder tests.",
|
||||||
|
[],
|
||||||
|
null!,
|
||||||
|
null!);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class ToolTarget
|
private sealed class ToolTarget
|
||||||
{
|
{
|
||||||
public string Echo() => "ok";
|
public string Echo() => "ok";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class StubChatClient : IChatClient
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ChatResponse> GetResponseAsync(
|
||||||
|
IEnumerable<ChatMessage> messages,
|
||||||
|
ChatOptions? options,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? GetService(Type serviceType, object? serviceKey = null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||||
|
IEnumerable<ChatMessage> messages,
|
||||||
|
ChatOptions? options,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user