Repository: microsoft/semantic-kernel
File: dotnet/src/SemanticKernel.Core/Functions/ContextualSelection/ContextualFunctionProvider.cs (C#)

Code before:

            this._areFunctionsVectorized = true;
        }

        // Build the context from the messages
        var context = await this.BuildContextAsync(cancellationToken).ConfigureAwait(false);

        // Get the function relevant to the context
        var functions = await this._functionStore
                .SearchAsync(context, cancellationToken: cancellationToken)
                .ConfigureAwait(false);

        return new AIContext { AIFunctions = [.. functions] };
    }

    /// <inheritdoc/>
    public override Task MessageAddingAsync(string? conversationId, ChatMessage newMessage, CancellationToken cancellationToken = default)
    {
        // Add the new message to the recent messages queue
        this._recentMessages.Enqueue(newMessage);

        // If there are more than ContextSize messages in the queue, remove the oldest ones
        for (int i = this._contextSize; i < this._recentMessages.Count; i++)
        {
            this._recentMessages.TryDequeue(out _);
        }

        return Task.CompletedTask;
    }

    /// <summary>
    /// Builds the context from chat messages.
    /// </summary>
    /// <param name="messages">The messages to build the context from.</param>
    /// <param name="cancellationToken">The cancellation token to use for cancellation.</param>
    private async Task<string> BuildContextAsync(CancellationToken cancellationToken)
    {
        if (this._options.ContextEmbeddingValueProvider is not null)
        {
            return await this._options.ContextEmbeddingValueProvider.Invoke([.. this._recentMessages], cancellationToken).ConfigureAwait(false);
        }

        return string.Join(
            Environment.NewLine,
            this._recentMessages.
                Where(m => !string.IsNullOrWhiteSpace(m?.Text)).
                Select(m => m.Text));
    }
}


Review comment (suggestion):
Have you considered the scenario where the user is invoking the agent with 4 new messages, and said that they want to use the last 2 for context.  You may argue that we should only use the last 2 of the 4 passed in, although that doesn't sound entirely right.  The user might rightfully expect that it should at least use all of the 4 currently passed in.

Code after:
        }

        // Build the context
        var context = await this.BuildContextAsync(newMessages, cancellationToken).ConfigureAwait(false);

        // Get the function relevant to the context
        var functions = await this._functionStore
                .SearchAsync(context, cancellationToken: cancellationToken)
                .ConfigureAwait(false);

        return new AIContext { AIFunctions = [.. functions] };
    }

    /// <inheritdoc/>
    public override Task MessageAddingAsync(string? conversationId, ChatMessage newMessage, CancellationToken cancellationToken = default)
    {
        // Add the new message to the recent messages queue
        this._recentMessages.Enqueue(newMessage);

        // If there are more messages than the configured limit, remove the oldest ones
        for (int i = RecentMessagesBufferSize + this._options.NumberOfRecentMessagesInContext; i < this._recentMessages.Count; i++)
        {
            this._recentMessages.TryDequeue(out _);
        }

        return Task.CompletedTask;
    }

    /// <summary>
    /// Builds the context from chat messages.
    /// </summary>
    /// <param name="newMessages">The new messages.</param>
    /// <param name="cancellationToken">The cancellation token to use for cancellation.</param>
    private async Task<string> BuildContextAsync(ICollection<ChatMessage> newMessages, CancellationToken cancellationToken)
    {
        if (this._options.ContextEmbeddingValueProvider is not null)
        {
            var recentMessages = this._recentMessages
                .Except(newMessages) // Exclude the new messages from the recent messages
                .TakeLast(this._options.NumberOfRecentMessagesInContext); // Ensure we only take the recent messages up to the configured limit

            return await this._options.ContextEmbeddingValueProvider.Invoke(recentMessages, newMessages, cancellationToken).ConfigureAwait(false);
        }

        // Build context from the recent messages that already include the new messages
        return string.Join(
            Environment.NewLine,
            this._recentMessages.TakeLast(newMessages.Count + this._options.NumberOfRecentMessagesInContext)
                .Where(m => !string.IsNullOrWhiteSpace(m?.Text))
                .Select(m => m.Text));
    }