Skip to content

Commit f9f568b

Browse files
Fix duplication between agents pre-configured tools and ChatOptions.Tools (Azure#53041)
Azure.AI.Agents.Persistent lets you configure an agent with function signatures it's implicitly aware of, rather than them needing to be provided per request. That, however, creates complication for callers via IChatClient, as if they provide that function in ChatTools.Options, it leads to the function's signature being sent as part of the request, and the duplication of it with the pre-configured function signature results in an error. It's possible to work around this, by simply not including that function in the request, but it's a natural thing for a developer to do, especially if they want the function to be automatically invoked when the model requests it. You can achieve that by putting the function into the FunctionInvocationChatClient's AdditionalTools, which exists for this kind of purpose, but that's harder to discover. Rather than try to do something complicated, this commit just deduplicates all function tools such that rather than getting an error when multiple tools have the same name, only one of them is kept. Co-authored-by: Roger Barreto <[email protected]>
1 parent 8cbb9df commit f9f568b

File tree

1 file changed

+21
-3
lines changed

1 file changed

+21
-3
lines changed

sdk/ai/Azure.AI.Agents.Persistent/src/Custom/PersistentAgentsChatClient.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ public void Dispose() { }
313313

314314
if (options.Tools is { Count: > 0 } tools)
315315
{
316-
List<ToolDefinition> toolDefinitions = [];
316+
HashSet<ToolDefinition> toolDefinitions = new(ToolDefinitionNameEqualityComparer.Instance);
317317
ToolResources? toolResources = null;
318318

319319
// If the caller has provided any tool overrides, we'll assume they don't want to use the agent's tools.
@@ -322,13 +322,13 @@ public void Dispose() { }
322322
// along with our tools.
323323
if (runOptions.OverrideTools is null || !runOptions.OverrideTools.Any())
324324
{
325-
toolDefinitions.AddRange(_agent.Tools);
325+
toolDefinitions.UnionWith(_agent.Tools);
326326
}
327327

328328
// The caller can provide tools in the supplied ThreadAndRunOptions.
329329
if (runOptions.OverrideTools is not null)
330330
{
331-
toolDefinitions.AddRange(runOptions.OverrideTools);
331+
toolDefinitions.UnionWith(runOptions.OverrideTools);
332332
}
333333

334334
// Now add the tools from ChatOptions.Tools.
@@ -692,5 +692,23 @@ internal sealed class ToolDefinitionAITool(ToolDefinition tool) : AITool
692692
[JsonSerializable(typeof(IDictionary<string, object>))]
693693
[JsonSerializable(typeof(IReadOnlyDictionary<string, object>))]
694694
private sealed partial class AgentsChatClientJsonContext : JsonSerializerContext;
695+
696+
/// <summary>
697+
/// Provides the same behavior as <see cref="EqualityComparer{ToolDefinition}.Default"/>, except
698+
/// for FunctionToolDefinition it compares names so that two function tool definitions with the
699+
/// same name compare equally.
700+
/// </summary>
701+
private sealed class ToolDefinitionNameEqualityComparer : IEqualityComparer<ToolDefinition>
702+
{
703+
public static ToolDefinitionNameEqualityComparer Instance { get; } = new();
704+
705+
public bool Equals(ToolDefinition? x, ToolDefinition? y) =>
706+
x is FunctionToolDefinition xFtd && y is FunctionToolDefinition yFtd ? xFtd.Name.Equals(yFtd.Name) :
707+
EqualityComparer<ToolDefinition?>.Default.Equals(x, y);
708+
709+
public int GetHashCode(ToolDefinition obj) =>
710+
obj is FunctionToolDefinition ftd ? ftd.Name.GetHashCode() :
711+
EqualityComparer<ToolDefinition>.Default.GetHashCode(obj);
712+
}
695713
}
696714
}

0 commit comments

Comments
 (0)