-
Notifications
You must be signed in to change notification settings - Fork 849
Ignore FunctionCallContent with paired FunctionResultContent in FunctionInvokingChatClient #7141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…unctionInvokingChatClient Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes FunctionInvokingChatClient to avoid re-invoking function calls that have already been handled (indicated by the presence of a matching FunctionResultContent with the same CallId). This resolves issues with multiple FunctionInvokingChatClients in a pipeline or inner clients that handle function invocation internally.
Key Changes
- Added filtering logic to identify and exclude
FunctionCallContentitems that have correspondingFunctionResultContentwith matching CallIds - Implemented O(1) lookup using
HashSet<string>to efficiently filter already-handled function calls - Extended approval-required function handling to skip FCCs with matching FRCs
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs |
Added two RemoveAlreadyHandledFunctionCalls methods for filtering; updated non-streaming and streaming paths to filter FCCs with matching FRCs; updated approval-request logic to exclude already-handled FCCs |
test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs |
Added comprehensive tests for non-streaming and streaming scenarios with FCCs and matching FRCs, including partial matches and multiple clients in pipeline |
test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs |
Updated existing test expectations for buffering behavior; added tests for approval-required functions with already-handled FCCs |
| [Fact] | ||
| public async Task MultipleFunctionInvokingClientsInPipeline_NonStreaming() | ||
| { | ||
| // When there are multiple FunctionInvokingChatClients in the pipeline, | ||
| // the outer one should not re-invoke functions already handled by the inner one. | ||
| int func1InvocationCount = 0; | ||
|
|
||
| var func1 = AIFunctionFactory.Create(() => { func1InvocationCount++; return "func1 result"; }, "Func1"); | ||
|
|
||
| var options = new ChatOptions { Tools = [func1] }; | ||
|
|
||
| int outerCallIndex = 0; | ||
| using var baseClient = new TestChatClient | ||
| { | ||
| GetResponseAsyncCallback = (chatContents, chatOptions, cancellationToken) => | ||
| { | ||
| outerCallIndex++; | ||
| if (outerCallIndex == 1) | ||
| { | ||
| // First call: return FCC | ||
| return Task.FromResult(new ChatResponse(new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1")]))); | ||
| } | ||
|
|
||
| // Second call: return final response | ||
| return Task.FromResult(new ChatResponse(new ChatMessage(ChatRole.Assistant, "done"))); | ||
| } | ||
| }; | ||
|
|
||
| // Create a pipeline with two FunctionInvokingChatClients | ||
| using var innerFunctionInvokingClient = new FunctionInvokingChatClient(baseClient); | ||
| using var outerFunctionInvokingClient = new FunctionInvokingChatClient(innerFunctionInvokingClient); | ||
|
|
||
| var result = await outerFunctionInvokingClient.GetResponseAsync([new ChatMessage(ChatRole.User, "hello")], options); | ||
|
|
||
| // The function should have been invoked exactly ONCE (by the inner FunctionInvokingChatClient) | ||
| // The outer one should see the FRC and not re-invoke | ||
| Assert.Equal(1, func1InvocationCount); | ||
| Assert.Equal("done", result.Text); | ||
| } |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test coverage gap: There's a test for multiple FunctionInvokingChatClients in the pipeline for non-streaming (MultipleFunctionInvokingClientsInPipeline_NonStreaming), but no corresponding streaming test. Consider adding a MultipleFunctionInvokingClientsInPipeline_Streaming test to ensure the filtering logic works correctly for this scenario in the streaming path.
FunctionInvokingChatClientwas attempting to invoke everyFunctionCallContentit encountered, even when a correspondingFunctionResultContentwith the sameCallIdwas already present in the response. This broke scenarios with multipleFunctionInvokingChatClients in the pipeline or inner clients that handle function invocation internally.Changes
RemoveAlreadyHandledFunctionCallsmethods that use aHashSet<string>for O(1) CallId lookups to identify and exclude FCCs with matching FRCsExample
Tests Added
FunctionInvokingChatClients in pipelineWarning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
securitytools.pkgs.visualstudio.com/opt/hostedtoolcache/CodeQL/2.23.7/x64/codeql/csharp/tools/linux64/Semmle.Autobuild.CSharp /opt/hostedtoolcache/CodeQL/2.23.7/x64/codeql/csharp/tools/linux64/Semmle.Autobuild.CSharp(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.
Microsoft Reviewers: Open in CodeFlow