Skip to content

Commit 73761aa

Browse files
westey-mCopilot
andauthored
.NET: Pass AdditionalProperties from parent to child when exposing an agent as a FunctionTool (#3219)
* Pass AdditionalProperties from parent to child when exposing an agent as a FunctionTool * Rename variable to improve readability. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 7429371 commit 73761aa

2 files changed

Lines changed: 66 additions & 1 deletion

File tree

dotnet/src/Microsoft.Agents.AI/AgentExtensions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ async Task<string> InvokeAgentAsync(
7373
[Description("Input query to invoke the agent.")] string query,
7474
CancellationToken cancellationToken)
7575
{
76-
var response = await agent.RunAsync(query, thread: thread, cancellationToken: cancellationToken).ConfigureAwait(false);
76+
// Propagate any additional properties from the parent agent's run to the child agent if the parent is using a FunctionInvokingChatClient.
77+
AgentRunOptions? agentRunOptions = FunctionInvokingChatClient.CurrentContext?.Options?.AdditionalProperties is AdditionalPropertiesDictionary dict
78+
? new AgentRunOptions { AdditionalProperties = dict }
79+
: null;
80+
81+
var response = await agent.RunAsync(query, thread: thread, options: agentRunOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
7782
return response.Text;
7883
}
7984

dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,48 @@ public async Task CreateFromAgent_InvokeWithComplexResponseFromAgentAsync_Return
277277
Assert.Equal("Complex response", result.ToString());
278278
}
279279

280+
[Fact]
281+
public async Task CreateFromAgent_InvokeWithAdditionalProperties_PropagatesAdditionalPropertiesToChildAgentAsync()
282+
{
283+
// Arrange
284+
var expectedResponse = new AgentResponse
285+
{
286+
AgentId = "agent-123",
287+
ResponseId = "response-456",
288+
CreatedAt = DateTimeOffset.UtcNow,
289+
Messages = { new ChatMessage(ChatRole.Assistant, "Complex response") }
290+
};
291+
292+
var testAgent = new TestAgent("TestAgent", "Test description", expectedResponse);
293+
var aiFunction = testAgent.AsAIFunction();
294+
295+
// Use reflection to set the protected CurrentContext property
296+
var context = new FunctionInvocationContext()
297+
{
298+
Options = new()
299+
{
300+
AdditionalProperties = new AdditionalPropertiesDictionary
301+
{
302+
{ "customProperty1", "value1" },
303+
{ "customProperty2", 42 }
304+
}
305+
}
306+
};
307+
SetFunctionInvokingChatClientCurrentContext(context);
308+
309+
// Act
310+
var arguments = new AIFunctionArguments() { ["query"] = "Test query" };
311+
var result = await aiFunction.InvokeAsync(arguments);
312+
313+
// Assert
314+
Assert.NotNull(result);
315+
Assert.Equal("Complex response", result.ToString());
316+
Assert.NotNull(testAgent.ReceivedAgentRunOptions);
317+
Assert.NotNull(testAgent.ReceivedAgentRunOptions!.AdditionalProperties);
318+
Assert.Equal("value1", testAgent.ReceivedAgentRunOptions!.AdditionalProperties["customProperty1"]);
319+
Assert.Equal(42, testAgent.ReceivedAgentRunOptions!.AdditionalProperties["customProperty2"]);
320+
}
321+
280322
[Theory]
281323
[InlineData("MyAgent", "MyAgent")]
282324
[InlineData("Agent123", "Agent123")]
@@ -302,6 +344,22 @@ public void CreateFromAgent_SanitizesAgentName(string agentName, string expected
302344
Assert.Equal(expectedFunctionName, result.Name);
303345
}
304346

347+
/// <summary>
348+
/// Uses reflection to set the protected static CurrentContext property on FunctionInvokingChatClient.
349+
/// </summary>
350+
private static void SetFunctionInvokingChatClientCurrentContext(FunctionInvocationContext? context)
351+
{
352+
// Access the private static field _currentContext which is an AsyncLocal<FunctionInvocationContext?>
353+
var currentContextField = typeof(FunctionInvokingChatClient).GetField(
354+
"_currentContext",
355+
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
356+
357+
if (currentContextField?.GetValue(null) is AsyncLocal<FunctionInvocationContext?> asyncLocal)
358+
{
359+
asyncLocal.Value = context;
360+
}
361+
}
362+
305363
/// <summary>
306364
/// Test implementation of AIAgent for testing purposes.
307365
/// </summary>
@@ -334,6 +392,7 @@ public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serial
334392
public override string? Description { get; }
335393

336394
public List<ChatMessage> ReceivedMessages { get; } = [];
395+
public AgentRunOptions? ReceivedAgentRunOptions { get; private set; }
337396
public CancellationToken LastCancellationToken { get; private set; }
338397
public int RunAsyncCallCount { get; private set; }
339398

@@ -346,6 +405,7 @@ protected override Task<AgentResponse> RunCoreAsync(
346405
this.RunAsyncCallCount++;
347406
this.LastCancellationToken = cancellationToken;
348407
this.ReceivedMessages.AddRange(messages);
408+
this.ReceivedAgentRunOptions = options;
349409

350410
if (this._exceptionToThrow is not null)
351411
{

0 commit comments

Comments
 (0)