-
Notifications
You must be signed in to change notification settings - Fork 850
Fix FunctionInvokingChatClient to respect ChatOptions.Tools modifications by function tools #7218
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
…ions by function tools Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
|
@copilot, would it make more sense / be cleaner to just get rid of the map entirely? I believe we added it originally out of concern for the case where there are lots of tools and lots of function requests, but it's rare to have more than a few function requests in a single response, if we consider the number of tools to be a constant controlled by the client then we're still O(N) in the number of tool requests, and having to keep the map up to date adds even more overhead that the 90% case is having to pay. |
Per reviewer feedback, removed the Dictionary-based tool map entirely and replaced it with linear search through AdditionalTools and options.Tools. This simplifies the code and removes the overhead of maintaining/refreshing the map, which is acceptable since: - Most function invocations have only a few function calls - Tool lists are typically small - Linear search avoids dictionary allocation and synchronization overhead Added helper methods: - FindTool: Linear search for a tool by name - AnyToolsRequireApproval: Checks if any tool requires approval - HasAnyTools: Checks if there are any tools in the lists Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Good point. Removed the map entirely in fc8c227. Now using linear search through |
| int consecutiveErrorCount = 0; | ||
|
|
||
| (Dictionary<string, AITool>? toolMap, bool anyToolsRequireApproval) = CreateToolsMap(AdditionalTools, options?.Tools); // all available tools, indexed by name | ||
| bool anyToolsRequireApproval = AnyToolsRequireApproval(AdditionalTools, options?.Tools); |
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.
@copilot, do we need this here? What's consuming this value before it's recomputed in the for loop below?
| int consecutiveErrorCount = 0; | ||
|
|
||
| (Dictionary<string, AITool>? toolMap, bool anyToolsRequireApproval) = CreateToolsMap(AdditionalTools, options?.Tools); // all available tools, indexed by name | ||
| bool anyToolsRequireApproval = AnyToolsRequireApproval(AdditionalTools, options?.Tools); |
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.
@copilot, same question
| private static AITool? FindTool(string name, params ReadOnlySpan<IList<AITool>?> toolLists) | ||
| { | ||
| // Search in reverse order so that tools from later lists (e.g., options.Tools) | ||
| // take precedence over tools from earlier lists (e.g., AdditionalTools). |
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.
@copilot, would it be simpler to just change the contract so that tools from earlier lists take precedence over tools from later lists? Then this could just be a foreach. We'd just need to fix up call sites, assuming it's possible and easy.
| { | ||
| for (int j = 0; j < count; j++) | ||
| { | ||
| if (string.Equals(toolList[j].Name, name, StringComparison.Ordinal)) |
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.
@copilot, this condition should also include whether the tool is an AIFunctionDeclaration, right?
| anyApprovalRequired = true; | ||
| break; | ||
| } | ||
| anyApprovalRequired = true; |
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.
@copilot, this could be simplified to:
anyApprovalRequired |= FindTool(functionCall.Name, toolLists)?.GetService<ApprovalRequiredAIFunction>() is not null;| [Theory] | ||
| [InlineData(false)] | ||
| [InlineData(true)] | ||
| public async Task RespectsChatOptionsToolsModificationsByFunctionTool_AddTool(bool streaming) |
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.
@copilot, please ensure there are also tests validating when the added/removed/changed functions require approval.
NeedsToolMapRefreshmethod to detect when tools have changedCreateToolsMapandNeedsToolMapRefreshmethodsFindToolhelper for linear search through tool listsAnyToolsRequireApprovalhelper to check if any tool requires approvalHasAnyToolshelper to check if there are any toolsOriginal prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.
Microsoft Reviewers: Open in CodeFlow