diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantChatClient.cs index c3aab83da61..f3717f1ee41 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantChatClient.cs @@ -44,6 +44,9 @@ internal sealed partial class OpenAIAssistantChatClient : IChatClient /// The thread ID to use if none is supplied in . private readonly string? _defaultThreadId; + /// List of tools associated with the assistant. + private IReadOnlyList? _assistantTools; + /// Initializes a new instance of the class for the specified . public OpenAIAssistantChatClient(AssistantClient assistantClient, string assistantId, string? defaultThreadId) { @@ -83,7 +86,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( _ = Throw.IfNull(messages); // Extract necessary state from messages and options. - (RunCreationOptions runOptions, List? toolResults) = CreateRunOptions(messages, options); + (RunCreationOptions runOptions, List? toolResults) = await CreateRunOptionsAsync(messages, options, cancellationToken).ConfigureAwait(false); // Get the thread ID. string? threadId = options?.ConversationId ?? _defaultThreadId; @@ -238,8 +241,8 @@ void IDisposable.Dispose() /// Creates the to use for the request and extracts any function result contents /// that need to be submitted as tool results. /// - private (RunCreationOptions RunOptions, List? ToolResults) CreateRunOptions( - IEnumerable messages, ChatOptions? options) + private async ValueTask<(RunCreationOptions RunOptions, List? ToolResults)> CreateRunOptionsAsync( + IEnumerable messages, ChatOptions? options, CancellationToken cancellationToken) { // Create the options instance to populate, either a fresh or using one the caller provides. RunCreationOptions runOptions = @@ -257,6 +260,24 @@ void IDisposable.Dispose() if (options.Tools is { Count: > 0 } tools) { + // If the caller has provided any tool overrides, we'll assume they don't want to use the assistant's tools. + // But if they haven't, the only way we can provide our tools is via an override, whereas we'd really like to + // just add them. To handle that, we'll get all of the assistant's tools and add them to the override list + // along with our tools. + if (runOptions.ToolsOverride.Count == 0) + { + if (_assistantTools is null) + { + var assistant = await _client.GetAssistantAsync(_assistantId, cancellationToken).ConfigureAwait(false); + _assistantTools = assistant.Value.Tools; + } + + foreach (var tool in _assistantTools) + { + runOptions.ToolsOverride.Add(tool); + } + } + // The caller can provide tools in the supplied ThreadAndRunOptions. Augment it with any supplied via ChatOptions.Tools. foreach (AITool tool in tools) { @@ -290,7 +311,6 @@ void IDisposable.Dispose() runOptions.ToolConstraint = ToolConstraint.None; break; - case null: case AutoChatToolMode: runOptions.ToolConstraint = ToolConstraint.Auto; break;