-
Notifications
You must be signed in to change notification settings - Fork 4.4k
.Net: feat: Implement type-safe LINQ filtering for ITextSearch interface (microsoft#10456) #13175
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
Merged
markwallace-microsoft
merged 1 commit into
microsoft:feature-text-search-linq
from
alzarei:feature-text-search-linq-pr1
Sep 25, 2025
Merged
.Net: feat: Implement type-safe LINQ filtering for ITextSearch interface (microsoft#10456) #13175
markwallace-microsoft
merged 1 commit into
microsoft:feature-text-search-linq
from
alzarei:feature-text-search-linq-pr1
Sep 25, 2025
+151
−3
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Add ITextSearch<TRecord> generic interface with type-safe LINQ filtering - Add TextSearchOptions<TRecord> with Expression<Func<TRecord, bool>>? Filter property - Update VectorStoreTextSearch to implement both generic and legacy interfaces - Maintain full backward compatibility with existing ITextSearch interface - Enable compile-time type safety and eliminate TextSearchFilter conversion overhead Addresses microsoft#10456
Author
@microsoft-github-policy-service agree |
Author
|
@microsoft-github-policy-service agree |
Author
|
@markwallace-microsoft @moonbox3 can you please trigger the Merge Gatekeeper again? Thanks! |
markwallace-microsoft
approved these changes
Sep 25, 2025
b94f3f3
into
microsoft:feature-text-search-linq
14 of 15 checks passed
markwallace-microsoft
pushed a commit
that referenced
this pull request
Nov 3, 2025
…arch.GetSearchResultsAsync (#13318) This PR enhances the type safety of the `ITextSearch<TRecord>` interface by changing the `GetSearchResultsAsync` method to return `KernelSearchResults<TRecord>` instead of `KernelSearchResults<object>`. This improvement eliminates the need for manual casting and provides better IntelliSense support for consumers. ## Motivation and Context The current implementation of `ITextSearch<TRecord>.GetSearchResultsAsync` returns `KernelSearchResults<object>`, which requires consumers to manually cast results to the expected type. This reduces type safety and degrades the developer experience by losing compile-time type checking and IntelliSense support. This change aligns the return type with the generic type parameter `TRecord`, providing the expected strongly-typed results that users of a generic interface would anticipate. ## Changes Made ### Interface (ITextSearch.cs) - Changed `ITextSearch<TRecord>.GetSearchResultsAsync` return type from `KernelSearchResults<object>` to `KernelSearchResults<TRecord>` - Updated XML documentation to reflect strongly-typed return value - Legacy `ITextSearch` interface (non-generic) remains unchanged, continuing to return `KernelSearchResults<object>` for backward compatibility ### Implementation (VectorStoreTextSearch.cs) - Added new `GetResultsAsTRecordAsync` helper method returning `IAsyncEnumerable<TRecord>` - Updated generic interface implementation to use the new strongly-typed helper - Retained `GetResultsAsRecordAsync` method for the legacy non-generic interface ### Tests (VectorStoreTextSearchTests.cs) - Updated 3 unit tests to use strongly-typed `DataModel` or `DataModelWithRawEmbedding` instead of `object` - Improved test assertions to leverage direct property access without casting - All 19 tests pass successfully ## Breaking Changes **Interface Change (Experimental API):** - `ITextSearch<TRecord>.GetSearchResultsAsync` now returns `KernelSearchResults<TRecord>` instead of `KernelSearchResults<object>` - This interface is marked with `[Experimental("SKEXP0001")]`, indicating that breaking changes are expected during the preview period - Legacy `ITextSearch` interface (non-generic) is unaffected and maintains full backward compatibility ## Benefits - **Improved Type Safety**: Eliminates runtime casting errors by providing compile-time type checking - **Enhanced Developer Experience**: Full IntelliSense support for TRecord properties and methods - **Cleaner Code**: Consumers no longer need to cast results from object to the expected type - **Consistent API Design**: Generic interface now behaves as expected, returning strongly-typed results - **Zero Impact on Legacy Code**: Non-generic ITextSearch interface remains unchanged ## Testing - All 19 existing unit tests pass - Updated tests demonstrate improved type safety with direct property access - Verified both generic and legacy interfaces work correctly - Confirmed zero breaking changes to non-generic ITextSearch consumers ## Related Work This PR is part of the Issue #10456 multi-PR chain for modernizing ITextSearch with LINQ-based filtering: - PR #13175: Foundation (ITextSearch<TRecord> interface) - Merged - PR #13179: VectorStoreTextSearch + deprecation pattern - In Review - **This PR (2.1)**: API refinement for improved type safety - PR #13188-13191: Connector migrations (Bing, Google, Tavily, Brave) - Pending - PR #13194: Samples and documentation - Pending All PRs target the `feature-text-search-linq` branch for coordinated release. ## Migration Guide for Consumers ### Before (Previous API) ```csharp ITextSearch<DataModel> search = ...; KernelSearchResults<object> results = await search.GetSearchResultsAsync("query", options); foreach (var obj in results.Results) { var record = (DataModel)obj; // Manual cast required Console.WriteLine(record.Name); } ``` ### After (Improved API) ```csharp ITextSearch<DataModel> search = ...; KernelSearchResults<DataModel> results = await search.GetSearchResultsAsync("query", options); foreach (var record in results.Results) // Strongly typed! { Console.WriteLine(record.Name); // Direct property access with IntelliSense } ``` ## Checklist - [x] Changes build successfully - [x] All unit tests pass (19/19) - [x] XML documentation updated - [x] Breaking change documented (experimental API only) - [x] Legacy interface backward compatibility maintained - [x] Code follows project coding standards Co-authored-by: Alexander Zarei <alzarei@users.noreply.github.com>
This was referenced Nov 23, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Add generic ITextSearch interface with LINQ filtering support
Addresses Issue #10456: Modernize ITextSearch to use LINQ-based vector search filtering
Motivation and Context
Why is this change required?
The current ITextSearch interface uses legacy
TextSearchFilterwhich requires conversion to obsoleteVectorSearchFilter, creating technical debt and performance overhead. Issue #10456 requests modernization to use type-safe LINQ filtering withExpression<Func<TRecord, bool>>.What problem does it solve?
What scenario does it contribute to?
This enables developers to write type-safe text search filters like:
Issue Link: #10456
Description
This PR introduces foundational generic interfaces to enable LINQ-based filtering for text search operations. The implementation follows an additive approach, maintaining 100% backward compatibility while providing a modern, type-safe alternative.
Overall Approach:
ITextSearch<TRecord>interface alongside existing non-generic versionTextSearchOptions<TRecord>with LINQExpression<Func<TRecord, bool>>? FilterVectorStoreTextSearchto implement both interfacesUnderlying Design:
Engineering Approach: Following Microsoft's Established Patterns
This solution was not created from scratch but carefully architected by studying and extending Microsoft's existing patterns within the Semantic Kernel codebase:
1. Pattern Discovery: VectorSearchOptions Template
Found the exact migration pattern Microsoft established in PR #10273:
2. Existing Infrastructure Analysis
Discovered that
VectorStoreTextSearch.csalready had the implementation infrastructure:3. Microsoft's Additive Migration Strategy
Followed the exact pattern used across the codebase:
[Experimental]attributes for new features4. Consistency with Existing Filter Translators
All vector database connectors (AzureAISearch, Qdrant, MongoDB, Weaviate) use the same pattern:
5. Technical Debt Elimination
The existing problematic code that this PR enables fixing in PR #2:
This will be replaced with direct LINQ filtering:
Filter = searchOptions.FilterResult: This solution extends Microsoft's established patterns consistently rather than introducing new conventions, ensuring seamless integration with the existing ecosystem.
Summary
This PR introduces the foundational generic interfaces needed to modernize text search functionality from legacy
TextSearchFilterto type-safe LINQExpression<Func<TRecord, bool>>filtering. This is the first in a series of PRs to completely resolve Issue #10456.Key Changes
New Generic Interfaces
ITextSearch<TRecord>: Generic interface with type-safe LINQ filteringSearchAsync<TRecord>(string query, TextSearchOptions<TRecord> options, CancellationToken cancellationToken)GetTextSearchResultsAsync<TRecord>(string query, TextSearchOptions<TRecord> options, CancellationToken cancellationToken)GetSearchResultsAsync<TRecord>(string query, TextSearchOptions<TRecord> options, CancellationToken cancellationToken)TextSearchOptions<TRecord>: Generic options class with LINQ supportExpression<Func<TRecord, bool>>? Filterproperty for compile-time type safetyEnhanced Implementation
VectorStoreTextSearch<TValue>: Now implements both generic and legacy interfacesITextSearchITextSearch<TValue>with direct LINQ filteringTextSearchFilter→ obsoleteVectorSearchFilterconversionBenefits
Type Safety & Developer Experience
Performance Improvements
Zero Breaking Changes
ITextSearchandTextSearchOptionsuntouchedImplementation Strategy
This PR implements Phase 1 of the Issue #10456 resolution across 6 structured PRs:
[DONE] PR 1 (This PR): Core generic interface additions
ITextSearch<TRecord>andTextSearchOptions<TRecord>interfacesVectorStoreTextSearchto implement both legacy and generic interfaces[TODO] PR 2: VectorStoreTextSearch internal modernization
VectorSearchFilterconversion overhead[TODO] PR 3: Modernize BingTextSearch connector
BingTextSearch.csto implementITextSearch<TRecord>[TODO] PR 4: Modernize GoogleTextSearch connector
GoogleTextSearch.csto implementITextSearch<TRecord>[TODO] PR 5: Modernize remaining connectors
TavilyTextSearch.csandBraveTextSearch.cs[TODO] PR 6: Tests and samples modernization
Verification Results
Microsoft Official Pre-Commit Compliance
Test Coverage
Code Quality
Example Usage
Before (Legacy)
After (Generic with LINQ)
Files Modified
Contribution Checklist
Verification Evidence:
dotnet build --configuration Release- 0 warnings, 0 errorsdotnet test --configuration Release- 1,574/1,574 tests passed (100%)dotnet format SK-dotnet.slnx --verify-no-changes- 0/10,131 files needed formattingIssue: #10456
Type: Enhancement (Feature Addition)
Breaking Changes: None
Documentation: Updated with comprehensive XML docs and usage examples