Skip to content

Commit 7631ef9

Browse files
qideqianwestey-m
andauthored
.Net: Add Filter to TextSearchProvider (#12662)
### Motivation and Context 1.Enable the Agent mode to utilize underlying Filter for filtering ### Description Same as Top, directly exposing the Filter of TextSearchOptions ### Contribution Checklist - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄 --------- Co-authored-by: westey <[email protected]>
1 parent 93a14d5 commit 7631ef9

File tree

3 files changed

+59
-2
lines changed

3 files changed

+59
-2
lines changed

dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public override async Task<AIContext> ModelInvokingAsync(ICollection<ChatMessage
7575

7676
var searchResults = await this._textSearch.GetTextSearchResultsAsync(
7777
input,
78-
new() { Top = this.Options.Top },
78+
new() { Top = this.Options.Top, Filter = this.Options.Filter },
7979
cancellationToken: cancellationToken).ConfigureAwait(false);
8080

8181
var results = await searchResults.Results.ToListAsync(cancellationToken).ConfigureAwait(false);
@@ -96,7 +96,7 @@ internal async Task<string> SearchAsync(string userQuestion, CancellationToken c
9696
{
9797
var searchResults = await this._textSearch.GetTextSearchResultsAsync(
9898
userQuestion,
99-
new() { Top = this.Options.Top },
99+
new() { Top = this.Options.Top, Filter = this.Options.Filter },
100100
cancellationToken: cancellationToken).ConfigureAwait(false);
101101

102102
var results = await searchResults.Results.ToListAsync(cancellationToken).ConfigureAwait(false);

dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public int Top
3333
}
3434
}
3535

36+
/// <summary>
37+
/// Gets or sets the filter expression to apply to the search query.
38+
/// </summary>
39+
public TextSearchFilter? Filter { get; init; }
40+
3641
/// <summary>
3742
/// Gets or sets the time at which the text search is performed.
3843
/// </summary>

dotnet/src/SemanticKernel.UnitTests/Data/TextSearchProviderTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,56 @@ public async Task ModelInvokingShouldUseOverrideContextFormatterIfProvidedAsync(
263263
// Assert
264264
Assert.Equal("Custom formatted context with 2 results.", result.Instructions);
265265
}
266+
267+
[Fact]
268+
public async Task SearchAsyncRespectsFilterOption()
269+
{
270+
// Arrange
271+
var mockTextSearch = new Mock<ITextSearch>();
272+
var searchResults = new Mock<IAsyncEnumerable<TextSearchResult>>();
273+
var mockEnumerator = new Mock<IAsyncEnumerator<TextSearchResult>>();
274+
275+
// Simulate the filtered results
276+
var filteredResult = new TextSearchResult("Filtered Content") { Name = "FilteredDoc", Link = "http://example.com/filtered" };
277+
var results = new List<TextSearchResult> { filteredResult };
278+
279+
mockEnumerator.SetupSequence(e => e.MoveNextAsync())
280+
.ReturnsAsync(true)
281+
.ReturnsAsync(false);
282+
283+
mockEnumerator.SetupSequence(e => e.Current)
284+
.Returns(filteredResult);
285+
286+
searchResults.Setup(r => r.GetAsyncEnumerator(It.IsAny<CancellationToken>()))
287+
.Returns(mockEnumerator.Object);
288+
289+
TextSearchFilter? capturedFilter = null;
290+
mockTextSearch.Setup(ts => ts.GetTextSearchResultsAsync(
291+
It.IsAny<string>(),
292+
It.IsAny<TextSearchOptions>(),
293+
It.IsAny<CancellationToken>()))
294+
.Callback<string, TextSearchOptions?, CancellationToken>((q, opts, ct) =>
295+
{
296+
capturedFilter = opts?.Filter;
297+
})
298+
.ReturnsAsync(new KernelSearchResults<TextSearchResult>(searchResults.Object));
299+
300+
var filter = new TextSearchFilter().Equality("Name", "FilteredDoc");
301+
var options = new TextSearchProviderOptions
302+
{
303+
Filter = filter
304+
};
305+
306+
var provider = new TextSearchProvider(mockTextSearch.Object, options: options);
307+
308+
// Act
309+
var result = await provider.SearchAsync("Sample user question?", CancellationToken.None);
310+
311+
// Assert
312+
Assert.Contains("Filtered Content", result);
313+
Assert.Contains("SourceDocName: FilteredDoc", result);
314+
Assert.Contains("SourceDocLink: http://example.com/filtered", result);
315+
Assert.NotNull(capturedFilter);
316+
Assert.Equal(filter, capturedFilter);
317+
}
266318
}

0 commit comments

Comments
 (0)