From 31dceeb595c30aad1ab5be8d70c70ad483596b20 Mon Sep 17 00:00:00 2001 From: Alexander Zarei Date: Sat, 28 Feb 2026 01:31:31 -0800 Subject: [PATCH 1/5] Address roji/westey review feedback round 2 on PR #13384 - Add [Obsolete] to FilterClause, EqualToFilterClause, AnyTagEqualToFilterClause - Add [Obsolete] to non-generic TextSearchOptions and TextSearchFilter - Refactor Google connector: merge 8 methods into 3 using pattern matching - Refactor Bing/Brave/Tavily: pattern matching, remove MemoryExtensions - Add explicit error for collection Contains (Enumerable.Contains) in Google/Bing - Handle null-guard patterns (x != null) in Google expression processing - Remove dead code (MapGoogleFilterToProperty) - Consolidate pragma CS0618 to file-level in all affected files - ExtractFiltersFromLegacy: throw on unsupported FilterClause types --- .../Plugins.Web/Bing/BingTextSearch.cs | 94 ++---- .../Plugins.Web/Brave/BraveTextSearch.cs | 108 ++----- .../Plugins.Web/Google/GoogleTextSearch.cs | 294 ++++++------------ .../Plugins.Web/Tavily/TavilyTextSearch.cs | 106 ++----- .../Data/TextSearch/TextSearchFilter.cs | 4 + .../Data/TextSearch/TextSearchOptions.cs | 2 + .../UnitTests/Search/MockTextSearch.cs | 4 +- .../Data/TextSearch/TextSearchExtensions.cs | 2 +- .../Data/TextSearch/VectorStoreTextSearch.cs | 8 +- .../TextSearchProviderOptions.cs | 2 + .../Data/MockTextSearch.cs | 4 +- .../Data/VectorStoreTextSearchTests.cs | 6 +- .../AnyTagEqualToFilterClause.cs | 3 + .../FilterClauses/EqualToFilterClause.cs | 3 + .../FilterClauses/FilterClause.cs | 3 + 15 files changed, 198 insertions(+), 445 deletions(-) diff --git a/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs index a0fd5d1f1bee..d16d7831d1d1 100644 --- a/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Non-generic ITextSearch is obsolete - provides backward compatibility during Phase 2 LINQ migration +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility using System; using System.Collections.Generic; @@ -23,9 +23,7 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Bing; /// /// A Bing Text Search implementation that can be used to perform searches using the Bing Web Search API. /// -#pragma warning disable CS0618 // ITextSearch is obsolete - this class provides backward compatibility public sealed class BingTextSearch : ITextSearch, ITextSearch -#pragma warning restore CS0618 { /// /// Create an instance of the with API key authentication. @@ -172,67 +170,48 @@ private static void ProcessExpression(Expression expression, List<(string FieldN { switch (expression) { - case BinaryExpression binaryExpr when binaryExpr.NodeType == ExpressionType.AndAlso: + case BinaryExpression { NodeType: ExpressionType.AndAlso } andExpr: // Handle AND: page => page.Language == "en" && page.Name.Contains("AI") - ProcessExpression(binaryExpr.Left, filters); - ProcessExpression(binaryExpr.Right, filters); + ProcessExpression(andExpr.Left, filters); + ProcessExpression(andExpr.Right, filters); break; - case BinaryExpression binaryExpr when binaryExpr.NodeType == ExpressionType.OrElse: + case BinaryExpression { NodeType: ExpressionType.OrElse }: // Handle OR: Currently not directly supported by TextSearchFilter // Bing API supports OR via multiple queries, but TextSearchFilter doesn't expose this throw new NotSupportedException( "Logical OR (||) is not supported by Bing Text Search filters. " + "Consider splitting into multiple search queries."); - case UnaryExpression unaryExpr when unaryExpr.NodeType == ExpressionType.Not: + case UnaryExpression { NodeType: ExpressionType.Not }: // Handle NOT: page => !page.Language.Equals("en") throw new NotSupportedException( "Logical NOT (!) is not directly supported by Bing Text Search advanced operators. " + "Consider restructuring your filter to use positive conditions."); - case BinaryExpression binaryExpr when binaryExpr.NodeType == ExpressionType.Equal: + case BinaryExpression { NodeType: ExpressionType.Equal } binaryExpr: // Handle equality: page => page.Language == "en" ProcessEqualityExpression(binaryExpr, filters, isNegated: false); break; - case BinaryExpression binaryExpr when binaryExpr.NodeType == ExpressionType.NotEqual: + case BinaryExpression { NodeType: ExpressionType.NotEqual } binaryExpr: // Handle inequality: page => page.Language != "en" // Implemented via Bing's negation syntax (e.g., -language:en) ProcessEqualityExpression(binaryExpr, filters, isNegated: true); break; - case MethodCallExpression methodExpr when methodExpr.Method.Name == "Contains": - // Distinguish between instance method (String.Contains) and static method (Enumerable/MemoryExtensions.Contains) - if (methodExpr.Object is MemberExpression) - { - // Instance method: page.Name.Contains("value") - SUPPORTED - ProcessContainsExpression(methodExpr, filters); - } - else if (methodExpr.Object == null) - { - // Static method: could be Enumerable.Contains (C# 13-) or MemoryExtensions.Contains (C# 14+) - // Bing API doesn't support OR logic, so collection Contains patterns are not supported - if (methodExpr.Method.DeclaringType == typeof(Enumerable) || - (methodExpr.Method.DeclaringType == typeof(MemoryExtensions) && IsMemoryExtensionsContains(methodExpr))) - { - throw new NotSupportedException( - "Collection Contains filters (e.g., array.Contains(page.Property)) are not supported by Bing Search API. " + - "Bing's advanced search operators do not support OR logic across multiple values. " + - "Supported pattern: Property.Contains(\"value\") for string properties like Name, Snippet, or Url. " + - "For multiple value matching, consider alternative approaches or use a different search provider."); - } - - throw new NotSupportedException( - $"Contains() method from {methodExpr.Method.DeclaringType?.Name} is not supported."); - } - else - { - throw new NotSupportedException( - "Contains() must be called on a property (e.g., page.Name.Contains(\"value\"))."); - } + case MethodCallExpression { Method.Name: "Contains", Object: MemberExpression } methodExpr: + // Instance method: page.Name.Contains("value") - SUPPORTED + ProcessContainsExpression(methodExpr, filters); break; + case MethodCallExpression { Method.Name: "Contains", Object: null }: + // Static method: collection.Contains(page.Property) - NOT SUPPORTED + throw new NotSupportedException( + "Collection Contains filters (e.g., collection.Contains(page.Property)) are not supported by Bing Search API. " + + "Bing API doesn't support OR logic across multiple values for a single field. " + + "Consider using multiple separate queries instead."); + default: throw new NotSupportedException( $"Expression type '{expression.NodeType}' is not supported for Bing API filters. " + @@ -360,32 +339,13 @@ private static void ProcessContainsExpression(MethodCallExpression methodExpr, L } } - /// - /// Determines if a MethodCallExpression is a MemoryExtensions.Contains call (C# 14 "first-class spans" feature). - /// - /// The method call expression to check. - /// True if this is a MemoryExtensions.Contains call with supported parameters; otherwise false. - private static bool IsMemoryExtensionsContains(MethodCallExpression methodExpr) - { - // MemoryExtensions.Contains has 2-3 parameters: - // - Contains(ReadOnlySpan span, T value) - // - Contains(ReadOnlySpan span, T value, IEqualityComparer? comparer) - // We only support when comparer is null or omitted - return methodExpr.Method.Name == nameof(MemoryExtensions.Contains) && - methodExpr.Arguments.Count >= 2 && - methodExpr.Arguments.Count <= 3 && - (methodExpr.Arguments.Count == 2 || - (methodExpr.Arguments.Count == 3 && methodExpr.Arguments[2] is ConstantExpression { Value: null })); - } - /// /// Maps BingWebPage property names to Bing API filter field names for equality operations. /// /// The BingWebPage property name. /// The corresponding Bing API filter name, or null if not mappable. - private static string? MapPropertyToBingFilter(string propertyName) - { - return propertyName.ToUpperInvariant() switch + private static string? MapPropertyToBingFilter(string propertyName) => + propertyName.ToUpperInvariant() switch { // Map BingWebPage properties to Bing API equivalents "LANGUAGE" => "language", // Maps to advanced search @@ -401,16 +361,14 @@ private static bool IsMemoryExtensionsContains(MethodCallExpression methodExpr) _ => null // Property not mappable to Bing filters }; - } /// /// Maps BingWebPage property names to Bing API advanced search operators for Contains operations. /// /// The BingWebPage property name. /// The corresponding Bing advanced search operator, or null if not mappable. - private static string? MapPropertyToContainsFilter(string propertyName) - { - return propertyName.ToUpperInvariant() switch + private static string? MapPropertyToContainsFilter(string propertyName) => + propertyName.ToUpperInvariant() switch { // Map properties to Bing's contains-style operators "NAME" => "intitle", // intitle:"search term" - title contains @@ -420,7 +378,6 @@ private static bool IsMemoryExtensionsContains(MethodCallExpression methodExpr) _ => null // Property not mappable to Contains-style filters }; - } /// /// Execute a Bing search query and return the results. @@ -594,7 +551,6 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) } } -#pragma warning disable CS0618 // FilterClause is obsolete - backward compatibility shim for legacy ITextSearch /// /// Extracts filter key-value pairs from a legacy . /// This shim converts the obsolete FilterClause-based format to the internal (FieldName, Value) list. @@ -611,11 +567,15 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) { filters.Add((eq.FieldName, eq.Value)); } + else + { + throw new NotSupportedException( + $"Filter clause type '{clause.GetType().Name}' is not supported by Bing Text Search. Only EqualToFilterClause is supported."); + } } } return filters; } -#pragma warning restore CS0618 /// /// Build a Bing API query string from pre-extracted filter key-value pairs. diff --git a/dotnet/src/Plugins/Plugins.Web/Brave/BraveTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Brave/BraveTextSearch.cs index 6417ecffd394..2f56cc4ee74a 100644 --- a/dotnet/src/Plugins/Plugins.Web/Brave/BraveTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Brave/BraveTextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility + using System; using System.Collections.Generic; using System.Linq; @@ -21,9 +23,7 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Brave; /// /// A Brave Text Search implementation that can be used to perform searches using the Brave Web Search API. /// -#pragma warning disable CS0618 // ITextSearch is obsolete - this class provides backward compatibility public sealed class BraveTextSearch : ITextSearch, ITextSearch -#pragma warning restore CS0618 { /// /// Create an instance of the with API key authentication. @@ -163,28 +163,24 @@ private static void ExtractFiltersFromExpression(Expression expression, List<(st { switch (expression) { - case BinaryExpression binaryExpr: - if (binaryExpr.NodeType is ExpressionType.AndAlso or ExpressionType.OrElse) - { - // Handle AND/OR expressions by recursively analyzing both sides - ExtractFiltersFromExpression(binaryExpr.Left, filters, queryTerms); - ExtractFiltersFromExpression(binaryExpr.Right, filters, queryTerms); - } - else if (binaryExpr.NodeType == ExpressionType.Equal) - { - ProcessEqualityClause(binaryExpr, filters); - } - else if (binaryExpr.NodeType == ExpressionType.NotEqual) - { - ProcessInequalityClause(binaryExpr); - } - else - { - throw new NotSupportedException($"Binary expression type '{binaryExpr.NodeType}' is not supported. Supported operators: AndAlso (&&), OrElse (||), Equal (==), NotEqual (!=)."); - } + case BinaryExpression { NodeType: ExpressionType.AndAlso or ExpressionType.OrElse } binaryExpr: + // Handle AND/OR expressions by recursively analyzing both sides + ExtractFiltersFromExpression(binaryExpr.Left, filters, queryTerms); + ExtractFiltersFromExpression(binaryExpr.Right, filters, queryTerms); + break; + + case BinaryExpression { NodeType: ExpressionType.Equal } binaryExpr: + ProcessEqualityClause(binaryExpr, filters); + break; + + case BinaryExpression { NodeType: ExpressionType.NotEqual } binaryExpr: + ProcessInequalityClause(binaryExpr); break; - case UnaryExpression unaryExpr when unaryExpr.NodeType == ExpressionType.Not: + case BinaryExpression binaryExpr: + throw new NotSupportedException($"Binary expression type '{binaryExpr.NodeType}' is not supported. Supported operators: AndAlso (&&), OrElse (||), Equal (==), NotEqual (!=)."); + + case UnaryExpression { NodeType: ExpressionType.Not } unaryExpr: ProcessNotExpression(unaryExpr); break; @@ -308,29 +304,12 @@ private static void ProcessMethodCallClause(MethodCallExpression methodCall, Lis } } } - else if (methodCall.Object == null && methodCall.Arguments.Count == 2) - { - // Static method: array.Contains(property) - NOT supported - string errorMessage = "Collection Contains filters (e.g., array.Contains(page.Property)) are not supported by Brave Search API. " + - "Brave's API does not support OR logic across multiple values. "; - - if (IsMemoryExtensionsContains(methodCall)) - { - errorMessage += "Note: This occurs when using C# 14+ language features with span-based Contains methods (MemoryExtensions.Contains). "; - } - else - { - errorMessage += "Note: This occurs with standard LINQ extension methods (Enumerable.Contains). "; - } - - errorMessage += "Consider either: (1) performing multiple separate searches for each value, or " + - "(2) retrieving broader results and filtering on the client side."; - - throw new NotSupportedException(errorMessage); - } else { - throw new NotSupportedException("Unsupported Contains expression format."); + throw new NotSupportedException( + "Collection Contains filters (e.g., array.Contains(page.Property)) are not supported by Brave Search API. " + + "Consider either: (1) performing multiple separate searches for each value, or " + + "(2) retrieving broader results and filtering on the client side."); } } else @@ -642,7 +621,6 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) } } -#pragma warning disable CS0618 // FilterClause is obsolete - backward compatibility shim for legacy ITextSearch /// /// Extracts filter key-value pairs from a legacy . /// This shim converts the obsolete FilterClause-based format to the internal (FieldName, Value) list. @@ -659,11 +637,15 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) { filters.Add((eq.FieldName, eq.Value)); } + else + { + throw new NotSupportedException( + $"Filter clause type '{clause.GetType().Name}' is not supported by Brave Text Search. Only EqualToFilterClause is supported."); + } } } return filters; } -#pragma warning restore CS0618 /// /// Build a Brave API query string from pre-extracted filter key-value pairs. @@ -760,41 +742,5 @@ private static void CheckQueryValidation(string queryParam, object value) } } - /// - /// Determines if a method call expression is a MemoryExtensions.Contains call (C# 14+ compatibility). - /// In C# 14+, array.Contains(property) may resolve to MemoryExtensions.Contains instead of Enumerable.Contains. - /// - /// The method call expression to check. - /// True if this is a MemoryExtensions.Contains call, false otherwise. - private static bool IsMemoryExtensionsContains(MethodCallExpression methodCall) - { - // Check if this is a static method call (Object is null) - if (methodCall.Object != null) - { - return false; - } - - // Check if it's MemoryExtensions.Contains - if (methodCall.Method.DeclaringType?.Name != "MemoryExtensions") - { - return false; - } - - // MemoryExtensions.Contains has 2-3 parameters: (ReadOnlySpan, T) or (ReadOnlySpan, T, IEqualityComparer) - if (methodCall.Arguments.Count < 2 || methodCall.Arguments.Count > 3) - { - return false; - } - - // For our text search scenarios, we don't support span comparers - if (methodCall.Arguments.Count == 3) - { - throw new NotSupportedException( - "MemoryExtensions.Contains with custom IEqualityComparer is not supported. " + - "Use simple array.Contains(property) expressions without custom comparers."); - } - - return true; - } #endregion } diff --git a/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs index 265675dfd839..5785326342b7 100644 --- a/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility + using System; using System.Collections.Generic; using System.Linq; @@ -19,9 +21,7 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Google; /// /// A Google Text Search implementation that can be used to perform searches using the Google Web Search API. /// -#pragma warning disable CS0618 // ITextSearch is obsolete - this class provides backward compatibility public sealed class GoogleTextSearch : ITextSearch, ITextSearch, IDisposable -#pragma warning restore CS0618 { /// /// Initializes a new instance of the class. @@ -153,214 +153,120 @@ private static (int Top, int Skip, bool IncludeTotalCount, List<(string FieldNam /// Thrown when the expression cannot be converted to Google filters. private static List<(string FieldName, object Value)> ExtractFiltersFromExpression(Expression> linqExpression) { - // Handle compound AND expressions: expr1 && expr2 - if (linqExpression.Body is BinaryExpression andExpr && andExpr.NodeType == ExpressionType.AndAlso) - { - var filters = new List<(string FieldName, object Value)>(); - CollectAndCombineFilters(andExpr, filters); - return filters; - } - - // Handle simple expressions using the shared processing logic - var result = new List<(string FieldName, object Value)>(); - if (TryProcessSingleExpression(linqExpression.Body, result)) - { - return result; - } - - // Generate helpful error message with supported patterns - var supportedProperties = s_queryParameters.Select(p => - MapGoogleFilterToProperty(p)).Where(p => p != null).Distinct(); - - throw new NotSupportedException( - $"LINQ expression '{linqExpression}' cannot be converted to Google API filters. " + - $"Supported patterns: {string.Join(", ", s_supportedPatterns)}. " + - $"Supported properties: {string.Join(", ", supportedProperties)}."); + var filters = new List<(string FieldName, object Value)>(); + ProcessFilterNode(linqExpression.Body, filters); + return filters; } /// - /// Recursively collects and combines filters from compound AND expressions. + /// Recursively processes a LINQ expression tree node and builds Google API filters. + /// Handles AND combinations, equality, inequality, string Contains, and negation patterns. /// - /// The expression to process. + /// The expression node to process. /// The filter list to accumulate results into. - private static void CollectAndCombineFilters(Expression expression, List<(string FieldName, object Value)> filters) + /// Thrown when the expression cannot be converted to Google filters. + private static void ProcessFilterNode(Expression expression, List<(string FieldName, object Value)> filters) { - if (expression is BinaryExpression binaryExpr && binaryExpr.NodeType == ExpressionType.AndAlso) - { - // Recursively process both sides of the AND - CollectAndCombineFilters(binaryExpr.Left, filters); - CollectAndCombineFilters(binaryExpr.Right, filters); - } - else + switch (expression) { - // Process individual expression using shared logic - TryProcessSingleExpression(expression, filters); - } - } + case BinaryExpression { NodeType: ExpressionType.AndAlso } andExpr: + ProcessFilterNode(andExpr.Left, filters); + ProcessFilterNode(andExpr.Right, filters); + break; - /// - /// Shared logic to process a single LINQ expression and add appropriate filters. - /// Consolidates duplicate code between ExtractFiltersFromExpression and CollectAndCombineFilters. - /// - /// The expression to process. - /// The filter list to add results to. - /// True if the expression was successfully processed, false otherwise. - private static bool TryProcessSingleExpression(Expression expression, List<(string FieldName, object Value)> filters) - { - // Handle equality: record.PropertyName == "value" - if (expression is BinaryExpression equalExpr && equalExpr.NodeType == ExpressionType.Equal) - { - return TryProcessEqualityExpression(equalExpr, filters); - } - - // Handle inequality (NOT): record.PropertyName != "value" - if (expression is BinaryExpression notEqualExpr && notEqualExpr.NodeType == ExpressionType.NotEqual) - { - return TryProcessInequalityExpression(notEqualExpr, filters); - } + case BinaryExpression { NodeType: ExpressionType.Equal, Left: MemberExpression member, Right: ConstantExpression constant } + when constant.Value is not null: + { + var filterName = MapPropertyToGoogleFilter(member.Member.Name) + ?? throw new NotSupportedException( + $"Property '{member.Member.Name}' is not supported for Google API equality filters. " + + $"Supported patterns: {string.Join(", ", s_supportedPatterns)}."); + filters.Add((filterName, constant.Value)); + break; + } - // Handle Contains method calls - if (expression is MethodCallExpression methodCall && methodCall.Method.Name == "Contains") - { - // String.Contains (instance method) - supported for substring search - if (methodCall.Method.DeclaringType == typeof(string)) + case BinaryExpression { NodeType: ExpressionType.NotEqual, Left: MemberExpression member, Right: ConstantExpression constant } + when constant.Value is not null: { - return TryProcessContainsExpression(methodCall, filters); + _ = MapPropertyToGoogleFilter(member.Member.Name) + ?? throw new NotSupportedException( + $"Property '{member.Member.Name}' is not supported for Google API inequality filters. " + + $"Supported patterns: {string.Join(", ", s_supportedPatterns)}."); + filters.Add(("excludeTerms", constant.Value)); + break; } - // Collection Contains (static methods) - NOT supported due to Google API limitations - // This handles both Enumerable.Contains (C# 13-) and MemoryExtensions.Contains (C# 14+) - // User's C# language version determines which method is resolved, but both are unsupported - if (methodCall.Object == null) // Static method + case MethodCallExpression { Method.Name: "Contains", Method.DeclaringType: Type dt, Object: MemberExpression member } methodCall + when dt == typeof(string) + && methodCall.Arguments is [ConstantExpression { Value: not null } argExpr]: { - // Enumerable.Contains or MemoryExtensions.Contains - if (methodCall.Method.DeclaringType == typeof(Enumerable) || - (methodCall.Method.DeclaringType == typeof(MemoryExtensions) && IsMemoryExtensionsContains(methodCall))) - { - throw new NotSupportedException( - "Collection Contains filters (e.g., array.Contains(page.Property)) are not supported by Google Custom Search API. " + - "Google's search operators do not support OR logic across multiple values. " + - "Consider either: (1) performing multiple separate searches for each value, or " + - "(2) retrieving broader results and filtering on the client side."); - } + var filterName = MapPropertyToGoogleFilter(member.Member.Name) + ?? throw new NotSupportedException( + $"Property '{member.Member.Name}' is not supported for Google API Contains filters. " + + $"Supported patterns: {string.Join(", ", s_supportedPatterns)}."); + // For Contains operations on text fields, use orTerms (more flexible than exactTerms) + filters.Add((filterName == "exactTerms" ? "orTerms" : filterName, argExpr.Value)); + break; } - } - // Handle NOT expressions: !record.PropertyName.Contains("value") - if (expression is UnaryExpression unaryExpr && unaryExpr.NodeType == ExpressionType.Not) - { - return TryProcessNotExpression(unaryExpr, filters); - } + // Null-guard patterns (page.Property != null, page.Property == null) — silently skip + case BinaryExpression { NodeType: ExpressionType.Equal or ExpressionType.NotEqual, Right: ConstantExpression { Value: null } }: + break; - return false; - } + case MethodCallExpression { Method.Name: "Contains", Object: null }: + throw new NotSupportedException( + "Collection Contains filters (e.g., collection.Contains(page.Property)) are not supported by Google Custom Search API. " + + "Google API doesn't support OR logic across multiple values for a single field. " + + "Consider using multiple separate queries instead."); - /// - /// Checks if a method call expression is MemoryExtensions.Contains. - /// This handles C# 14's "first-class spans" feature where collection.Contains(item) resolves to - /// MemoryExtensions.Contains instead of Enumerable.Contains. - /// - private static bool IsMemoryExtensionsContains(MethodCallExpression methodExpr) - { - // MemoryExtensions.Contains has 2-3 parameters (source, value, optional comparer) - // We only support the case without a comparer (or with null comparer) - return methodExpr.Method.Name == nameof(MemoryExtensions.Contains) && - methodExpr.Arguments.Count >= 2 && - methodExpr.Arguments.Count <= 3 && - (methodExpr.Arguments.Count == 2 || - (methodExpr.Arguments.Count == 3 && methodExpr.Arguments[2] is ConstantExpression { Value: null })); - } + case UnaryExpression { NodeType: ExpressionType.Not } unaryExpr: + ProcessNegatedFilterNode(unaryExpr.Operand, filters); + break; - /// - /// Processes equality expressions: record.PropertyName == "value" - /// - private static bool TryProcessEqualityExpression(BinaryExpression equalExpr, List<(string FieldName, object Value)> filters) - { - if (equalExpr.Left is MemberExpression memberExpr && equalExpr.Right is ConstantExpression constExpr) - { - string propertyName = memberExpr.Member.Name; - object? value = constExpr.Value; - string? googleFilterName = MapPropertyToGoogleFilter(propertyName); - if (googleFilterName != null && value != null) - { - filters.Add((googleFilterName, value)); - return true; - } + default: + throw new NotSupportedException( + $"Expression type '{expression.NodeType}' is not supported for Google API filters. " + + $"Supported patterns: {string.Join(", ", s_supportedPatterns)}."); } - return false; } /// - /// Processes inequality expressions: record.PropertyName != "value" + /// Processes negated expressions (! operator), mapping them to Google's excludeTerms parameter. + /// Handles both !(property == value) and !property.Contains(value) patterns. /// - private static bool TryProcessInequalityExpression(BinaryExpression notEqualExpr, List<(string FieldName, object Value)> filters) + /// The inner expression of the NOT operation. + /// The filter list to accumulate results into. + /// Thrown when the negated expression cannot be converted to Google filters. + private static void ProcessNegatedFilterNode(Expression expression, List<(string FieldName, object Value)> filters) { - if (notEqualExpr.Left is MemberExpression memberExpr && notEqualExpr.Right is ConstantExpression constExpr) + switch (expression) { - string propertyName = memberExpr.Member.Name; - object? value = constExpr.Value; - // Map to excludeTerms for text fields - if (propertyName.ToUpperInvariant() is "TITLE" or "SNIPPET" && value != null) + case BinaryExpression { NodeType: ExpressionType.Equal, Left: MemberExpression member, Right: ConstantExpression constant } + when constant.Value is not null: { - filters.Add(("excludeTerms", value)); - return true; + _ = MapPropertyToGoogleFilter(member.Member.Name) + ?? throw new NotSupportedException( + $"Property '{member.Member.Name}' is not supported for Google API negated equality filters."); + filters.Add(("excludeTerms", constant.Value)); + break; } - } - return false; - } - /// - /// Processes Contains expressions: record.PropertyName.Contains("value") - /// - private static bool TryProcessContainsExpression(MethodCallExpression methodCall, List<(string FieldName, object Value)> filters) - { - if (methodCall.Object is MemberExpression memberExpr && - methodCall.Arguments.Count == 1 && - methodCall.Arguments[0] is ConstantExpression constExpr) - { - string propertyName = memberExpr.Member.Name; - object? value = constExpr.Value; - string? googleFilterName = MapPropertyToGoogleFilter(propertyName); - if (googleFilterName != null && value != null) + case MethodCallExpression { Method.Name: "Contains", Method.DeclaringType: Type dt, Object: MemberExpression member } methodCall + when dt == typeof(string) + && methodCall.Arguments is [ConstantExpression { Value: not null } argExpr]: { - // For Contains operations on text fields, use exactTerms or orTerms - if (googleFilterName == "exactTerms") - { - filters.Add(("orTerms", value)); // More flexible than exactTerms - } - else - { - filters.Add((googleFilterName, value)); - } - return true; + _ = MapPropertyToGoogleFilter(member.Member.Name) + ?? throw new NotSupportedException( + $"Property '{member.Member.Name}' is not supported for Google API negated Contains filters."); + filters.Add(("excludeTerms", argExpr.Value)); + break; } - } - return false; - } - /// - /// Processes NOT expressions: !record.PropertyName.Contains("value") - /// - private static bool TryProcessNotExpression(UnaryExpression unaryExpr, List<(string FieldName, object Value)> filters) - { - if (unaryExpr.Operand is MethodCallExpression notMethodCall && - notMethodCall.Method.Name == "Contains" && - notMethodCall.Method.DeclaringType == typeof(string)) - { - if (notMethodCall.Object is MemberExpression memberExpr && - notMethodCall.Arguments.Count == 1 && - notMethodCall.Arguments[0] is ConstantExpression constExpr) - { - string propertyName = memberExpr.Member.Name; - object? value = constExpr.Value; - if (propertyName.ToUpperInvariant() is "TITLE" or "SNIPPET" && value != null) - { - filters.Add(("excludeTerms", value)); - return true; - } - } + default: + throw new NotSupportedException( + $"Negated expression type '{expression.NodeType}' is not supported for Google API filters. " + + $"Supported patterns: {string.Join(", ", s_supportedPatterns)}."); } - return false; } /// @@ -368,9 +274,8 @@ private static bool TryProcessNotExpression(UnaryExpression unaryExpr, List<(str /// /// The GoogleWebPage property name. /// The corresponding Google API filter name, or null if not mappable. - private static string? MapPropertyToGoogleFilter(string propertyName) - { - return propertyName.ToUpperInvariant() switch + private static string? MapPropertyToGoogleFilter(string propertyName) => + propertyName.ToUpperInvariant() switch { // Map GoogleWebPage properties to Google API equivalents "LINK" => "siteSearch", // Maps to site search @@ -388,26 +293,6 @@ private static bool TryProcessNotExpression(UnaryExpression unaryExpr, List<(str _ => null // Property not mappable to Google filters }; - } - - /// - /// Maps Google Custom Search API filter field names back to example GoogleWebPage property names. - /// Used for generating helpful error messages. - /// - /// The Google API filter name. - /// An example property name, or null if not mappable. - private static string? MapGoogleFilterToProperty(string googleFilterName) - { - return googleFilterName switch - { - "siteSearch" => "DisplayLink", - "exactTerms" => "Title", - "orTerms" => "Title", - "excludeTerms" => "Title", - "fileType" => "FileFormat", - _ => null - }; - } #endregion @@ -492,7 +377,6 @@ public void Dispose() return await search.ExecuteAsync(cancellationToken).ConfigureAwait(false); } -#pragma warning disable CS0618 // FilterClause is obsolete - backward compatibility shim for legacy ITextSearch /// /// Extracts filter key-value pairs from a legacy . /// This shim converts the obsolete FilterClause-based format to the internal (FieldName, Value) list. @@ -509,11 +393,15 @@ public void Dispose() { filters.Add((eq.FieldName, eq.Value)); } + else + { + throw new NotSupportedException( + $"Filter clause type '{clause.GetType().Name}' is not supported by Google Text Search. Only EqualToFilterClause is supported."); + } } } return filters; } -#pragma warning restore CS0618 /// /// Apply pre-extracted filter key-value pairs to the Google search request. diff --git a/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs index bc187c426c54..9933ec7ce160 100644 --- a/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility + using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -21,9 +23,7 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Tavily; /// /// A Tavily Text Search implementation that can be used to perform searches using the Tavily Web Search API. /// -#pragma warning disable CS0618 // ITextSearch is obsolete - this class provides backward compatibility public sealed class TavilyTextSearch : ITextSearch, ITextSearch -#pragma warning restore CS0618 { /// /// Create an instance of the with API key authentication. @@ -160,28 +160,24 @@ private static void ExtractFiltersFromExpression(Expression expression, List<(st { switch (expression) { - case BinaryExpression binaryExpr: - if (binaryExpr.NodeType is ExpressionType.AndAlso or ExpressionType.OrElse) - { - // Handle AND/OR expressions by recursively analyzing both sides - ExtractFiltersFromExpression(binaryExpr.Left, filters, queryTerms); - ExtractFiltersFromExpression(binaryExpr.Right, filters, queryTerms); - } - else if (binaryExpr.NodeType == ExpressionType.Equal) - { - ProcessEqualityClause(binaryExpr, filters); - } - else if (binaryExpr.NodeType == ExpressionType.NotEqual) - { - ProcessInequalityClause(binaryExpr); - } - else - { - throw new NotSupportedException($"Binary expression type '{binaryExpr.NodeType}' is not supported. Supported operators: AndAlso (&&), OrElse (||), Equal (==), NotEqual (!=)."); - } + case BinaryExpression { NodeType: ExpressionType.AndAlso or ExpressionType.OrElse } binaryExpr: + // Handle AND/OR expressions by recursively analyzing both sides + ExtractFiltersFromExpression(binaryExpr.Left, filters, queryTerms); + ExtractFiltersFromExpression(binaryExpr.Right, filters, queryTerms); break; - case UnaryExpression unaryExpr when unaryExpr.NodeType == ExpressionType.Not: + case BinaryExpression { NodeType: ExpressionType.Equal } binaryExpr: + ProcessEqualityClause(binaryExpr, filters); + break; + + case BinaryExpression { NodeType: ExpressionType.NotEqual } binaryExpr: + ProcessInequalityClause(binaryExpr); + break; + + case BinaryExpression binaryExpr: + throw new NotSupportedException($"Binary expression type '{binaryExpr.NodeType}' is not supported. Supported operators: AndAlso (&&), OrElse (||), Equal (==), NotEqual (!=)."); + + case UnaryExpression { NodeType: ExpressionType.Not } unaryExpr: ProcessNotExpression(unaryExpr); break; @@ -306,29 +302,12 @@ private static void ProcessMethodCallClause(MethodCallExpression methodCall, Lis } } } - else if (methodCall.Object == null && methodCall.Arguments.Count == 2) - { - // Static method: array.Contains(property) - NOT supported - string errorMessage = "Collection Contains filters (e.g., array.Contains(page.Property)) are not supported by Tavily Search API. " + - "Tavily's API does not support OR logic across multiple values. "; - - if (IsMemoryExtensionsContains(methodCall)) - { - errorMessage += "Note: This occurs when using C# 14+ language features with span-based Contains methods (MemoryExtensions.Contains). "; - } - else - { - errorMessage += "Note: This occurs with standard LINQ extension methods (Enumerable.Contains). "; - } - - errorMessage += "Consider either: (1) performing multiple separate searches for each value, or " + - "(2) retrieving broader results and filtering on the client side."; - - throw new NotSupportedException(errorMessage); - } else { - throw new NotSupportedException("Unsupported Contains expression format."); + throw new NotSupportedException( + "Collection Contains filters (e.g., array.Contains(page.Property)) are not supported by Tavily Search API. " + + "Consider either: (1) performing multiple separate searches for each value, or " + + "(2) retrieving broader results and filtering on the client side."); } } else @@ -648,7 +627,6 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) } } -#pragma warning disable CS0618 // FilterClause is obsolete - backward compatibility shim for legacy ITextSearch /// /// Extracts filter key-value pairs from a legacy . /// This shim converts the obsolete FilterClause-based format to the internal (FieldName, Value) list. @@ -665,11 +643,15 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) { filters.Add((eq.FieldName, eq.Value)); } + else + { + throw new NotSupportedException( + $"Filter clause type '{clause.GetType().Name}' is not supported by Tavily Text Search. Only EqualToFilterClause is supported."); + } } } return filters; } -#pragma warning restore CS0618 /// /// Build a Tavily API request from pre-extracted filter key-value pairs. @@ -755,39 +737,5 @@ private TavilySearchRequest BuildRequestContent(string query, int top, int skip, return new(strPayload, Encoding.UTF8, "application/json"); } - /// - /// Determines if a method call expression is a MemoryExtensions.Contains call (C# 14+ compatibility). - /// In C# 14+, array.Contains(property) may resolve to MemoryExtensions.Contains instead of Enumerable.Contains. - /// - /// The method call expression to check. - /// True if this is a MemoryExtensions.Contains call, false otherwise. - private static bool IsMemoryExtensionsContains(MethodCallExpression methodCall) - { - // Check if this is a static method call (Object is null) - if (methodCall.Object != null) - { - return false; - } - - // Check if it's MemoryExtensions.Contains - if (methodCall.Method.DeclaringType?.Name != "MemoryExtensions") - { - return false; - } - - // MemoryExtensions.Contains has 2-3 parameters: (ReadOnlySpan, T) or (ReadOnlySpan, T, IEqualityComparer) - if (methodCall.Arguments.Count < 2 || methodCall.Arguments.Count > 3) - { - return false; - } // For our text search scenarios, we don't support span comparers - if (methodCall.Arguments.Count == 3) - { - throw new NotSupportedException( - "MemoryExtensions.Contains with custom IEqualityComparer is not supported. " + - "Use simple array.Contains(property) expressions without custom comparers."); - } - - return true; - } #endregion } diff --git a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchFilter.cs b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchFilter.cs index d964fb1ecba1..872fa8ed7f2f 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchFilter.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchFilter.cs @@ -1,9 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.VectorData; +#pragma warning disable CS0618 // FilterClause is obsolete - TextSearchFilter itself is obsolete and references FilterClause + namespace Microsoft.SemanticKernel.Data; /// @@ -15,6 +18,7 @@ namespace Microsoft.SemanticKernel.Data; /// service filter the search results. /// [Experimental("SKEXP0001")] +[Obsolete("Use LINQ expressions via TextSearchOptions.Filter instead. This type will be removed in a future version.")] public sealed class TextSearchFilter { /// diff --git a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchOptions.cs b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchOptions.cs index 9375d34abd0f..e33501a7a0b9 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchOptions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchOptions.cs @@ -50,6 +50,8 @@ public sealed class TextSearchOptions /// /// Options which can be applied when using . /// +#pragma warning disable CS0618 // TextSearchFilter is obsolete - TextSearchOptions itself is obsolete +[Obsolete("Use TextSearchOptions with ITextSearch instead. This type will be removed in a future version.")] public sealed class TextSearchOptions { /// diff --git a/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/MockTextSearch.cs b/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/MockTextSearch.cs index 9ed0d43a87fa..098b24d7ebc0 100644 --- a/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/MockTextSearch.cs +++ b/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/MockTextSearch.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Testing obsolete ITextSearch, TextSearchOptions backward compatibility + using Microsoft.SemanticKernel.Data; namespace SemanticKernel.AotTests.UnitTests.Search; -#pragma warning disable CS0618 // Type or member is obsolete internal sealed class MockTextSearch : ITextSearch -#pragma warning restore CS0618 // Type or member is obsolete { private readonly KernelSearchResults? _objectResults; private readonly KernelSearchResults? _textSearchResults; diff --git a/dotnet/src/SemanticKernel.Core/Data/TextSearch/TextSearchExtensions.cs b/dotnet/src/SemanticKernel.Core/Data/TextSearch/TextSearchExtensions.cs index c326b939dca2..fe91492b5aba 100644 --- a/dotnet/src/SemanticKernel.Core/Data/TextSearch/TextSearchExtensions.cs +++ b/dotnet/src/SemanticKernel.Core/Data/TextSearch/TextSearchExtensions.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // ITextSearch is obsolete - these extension methods provide backward compatibility +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs b/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs index 86b0d963e84f..bfd4fb493f49 100644 --- a/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs +++ b/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -19,9 +21,7 @@ namespace Microsoft.SemanticKernel.Data; /// A Vector Store Text Search implementation that can be used to perform searches using a . /// [Experimental("SKEXP0001")] -#pragma warning disable CS0618 // ITextSearch is obsolete - this class provides backward compatibility public sealed class VectorStoreTextSearch<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TRecord> : ITextSearch, ITextSearch -#pragma warning restore CS0618 #pragma warning restore CA1711 // Identifiers should not have incorrect suffix { /// @@ -64,7 +64,6 @@ public VectorStoreTextSearch( IEmbeddingGenerator> embeddingGenerator, ITextSearchStringMapper? stringMapper = null, ITextSearchResultMapper? resultMapper = null, -#pragma warning disable CS0618 // Type or member is obsolete VectorStoreTextSearchOptions? options = null) : this( vectorSearchable, @@ -72,7 +71,6 @@ public VectorStoreTextSearch( stringMapper, resultMapper, options) -#pragma warning restore CS0618 // Type or member is obsolete { } @@ -328,7 +326,6 @@ private async IAsyncEnumerable> ExecuteVectorSearchA /// The to monitor for cancellation requests. private async IAsyncEnumerable> ExecuteVectorSearchCoreAsync(string query, VectorSearchOptions vectorSearchOptions, int top, [EnumeratorCancellation] CancellationToken cancellationToken) { -#pragma warning disable CS0618 // Type or member is obsolete if (this._textEmbeddingGeneration is not null) { var vectorizedQuery = await this._textEmbeddingGeneration!.GenerateEmbeddingAsync(query, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -340,7 +337,6 @@ private async IAsyncEnumerable> ExecuteVectorSearchC yield break; } -#pragma warning restore CS0618 // Type or member is obsolete await foreach (var result in this._vectorSearchable!.SearchAsync(query, top, vectorSearchOptions, cancellationToken).WithCancellation(cancellationToken).ConfigureAwait(false)) { diff --git a/dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs b/dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs index f6cd9008012b..cd1ecb58cea3 100644 --- a/dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs +++ b/dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete TextSearchFilter - backward compatibility + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/dotnet/src/SemanticKernel.UnitTests/Data/MockTextSearch.cs b/dotnet/src/SemanticKernel.UnitTests/Data/MockTextSearch.cs index 01746adf623e..f31302dad0b9 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Data/MockTextSearch.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Data/MockTextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Testing obsolete ITextSearch, TextSearchOptions backward compatibility + using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,9 +12,7 @@ namespace SemanticKernel.UnitTests.Data; /// /// Mock implementation of /// -#pragma warning disable CS0618 // Type or member is obsolete internal sealed class MockTextSearch(int count = 3, long totalCount = 30) : ITextSearch -#pragma warning restore CS0618 // Type or member is obsolete { /// public Task> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) diff --git a/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs b/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs index 75f4b090590e..f17196cdb70a 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Testing obsolete TextSearchFilter backward compatibility + using System; using System.Linq; using System.Threading.Tasks; @@ -12,7 +14,6 @@ namespace SemanticKernel.UnitTests.Data; public class VectorStoreTextSearchTests : VectorStoreTextSearchTestBase { -#pragma warning disable CS0618 // VectorStoreTextSearch with ITextEmbeddingGenerationService is obsolete [Fact] public void CanCreateVectorStoreTextSearchWithEmbeddingGenerationService() { @@ -29,7 +30,6 @@ public void CanCreateVectorStoreTextSearchWithEmbeddingGenerationService() // Assert. Assert.NotNull(sut); } -#pragma warning restore CS0618 [Fact] public void CanCreateVectorStoreTextSearchWithIVectorSearch() @@ -129,7 +129,6 @@ public async Task CanGetSearchResultsWithEmbeddingGeneratorAsync() Assert.All(results, result => Assert.IsType(result)); } -#pragma warning disable CS0618 // VectorStoreTextSearch with ITextEmbeddingGenerationService is obsolete [Fact] public async Task CanSearchWithEmbeddingGenerationServiceAsync() { @@ -168,7 +167,6 @@ public async Task CanGetSearchResultsWithEmbeddingGenerationServiceAsync() Assert.Equal(2, results.Count); } -#pragma warning restore CS0618 // VectorStoreTextSearch with ITextEmbeddingGenerationService is obsolete [Fact] public async Task CanFilterGetSearchResultsWithVectorizedSearchAsync() diff --git a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/AnyTagEqualToFilterClause.cs b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/AnyTagEqualToFilterClause.cs index f40e63faa940..268dbe8876dd 100644 --- a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/AnyTagEqualToFilterClause.cs +++ b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/AnyTagEqualToFilterClause.cs @@ -1,10 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. +using System; + namespace Microsoft.Extensions.VectorData; /// /// Represents a filter clause that filters by checking if a field consisting of a list of values contains a specific value. /// +[Obsolete("Use LINQ expressions via TextSearchOptions.Filter instead. This type will be removed in a future version.")] public sealed class AnyTagEqualToFilterClause : FilterClause { /// diff --git a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/EqualToFilterClause.cs b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/EqualToFilterClause.cs index 89865732bd75..0ee049217b26 100644 --- a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/EqualToFilterClause.cs +++ b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/EqualToFilterClause.cs @@ -1,10 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. +using System; + namespace Microsoft.Extensions.VectorData; /// /// Represents a filter clause that filters using equality of a field value. /// +[Obsolete("Use LINQ expressions via TextSearchOptions.Filter instead. This type will be removed in a future version.")] public sealed class EqualToFilterClause : FilterClause { /// diff --git a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/FilterClause.cs b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/FilterClause.cs index be72560ffc2f..e0b3231952c1 100644 --- a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/FilterClause.cs +++ b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/FilterClause.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +using System; + namespace Microsoft.Extensions.VectorData; /// @@ -9,6 +11,7 @@ namespace Microsoft.Extensions.VectorData; /// A is used to request that the underlying search service should /// filter search results based on the specified criteria. /// +[Obsolete("Use LINQ expressions via TextSearchOptions.Filter instead. This type will be removed in a future version.")] public abstract class FilterClause { /// From 45e852b9a27cfc497668c432efee46156b0618eb Mon Sep 17 00:00:00 2001 From: Alexander Zarei Date: Sat, 28 Feb 2026 23:37:14 -0800 Subject: [PATCH 2/5] Add CS0618 pragma to samples and tests consuming obsolete TextSearchOptions/TextSearchFilter --- dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs | 3 +++ .../Concepts/Search/Bing_FunctionCallingWithTextSearch.cs | 2 ++ dotnet/samples/Concepts/Search/Bing_TextSearch.cs | 2 ++ dotnet/samples/Concepts/Search/Google_TextSearch.cs | 2 ++ dotnet/samples/Concepts/Search/Tavily_TextSearch.cs | 2 ++ .../Step3_Search_With_FunctionCalling.cs | 3 +++ .../AgentWithTextSearchProvider.cs | 4 ++-- 7 files changed, 16 insertions(+), 2 deletions(-) diff --git a/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs b/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs index 585c29cb0258..27e036d304cf 100644 --- a/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs +++ b/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs @@ -1,4 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter + using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Data; using Microsoft.SemanticKernel.Plugins.Web.Bing; diff --git a/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs b/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs index 0245eb80757e..23cfaf88ec07 100644 --- a/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs +++ b/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter + using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.Data; diff --git a/dotnet/samples/Concepts/Search/Bing_TextSearch.cs b/dotnet/samples/Concepts/Search/Bing_TextSearch.cs index b862360d740c..8114194dd857 100644 --- a/dotnet/samples/Concepts/Search/Bing_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Bing_TextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter + using System.Text.Json; using Microsoft.SemanticKernel.Data; using Microsoft.SemanticKernel.Plugins.Web.Bing; diff --git a/dotnet/samples/Concepts/Search/Google_TextSearch.cs b/dotnet/samples/Concepts/Search/Google_TextSearch.cs index 7d8c59478d7b..23561f8ef8a5 100644 --- a/dotnet/samples/Concepts/Search/Google_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Google_TextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter + using System.Text.Json; using Google.Apis.Http; using Microsoft.SemanticKernel.Data; diff --git a/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs b/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs index 82161b28dd63..b91bbc8948e0 100644 --- a/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter + using System.Text.Json; using Microsoft.SemanticKernel.Data; using Microsoft.SemanticKernel.Plugins.Web.Tavily; diff --git a/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs b/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs index 5b7766b589d3..e7e699794542 100644 --- a/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs +++ b/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs @@ -1,4 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter + using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; diff --git a/dotnet/src/IntegrationTests/Agents/CommonInterfaceConformance/AgentWithTextSearchProviderConformance/AgentWithTextSearchProvider.cs b/dotnet/src/IntegrationTests/Agents/CommonInterfaceConformance/AgentWithTextSearchProviderConformance/AgentWithTextSearchProvider.cs index 89e0a1790648..9dfc543e2635 100644 --- a/dotnet/src/IntegrationTests/Agents/CommonInterfaceConformance/AgentWithTextSearchProviderConformance/AgentWithTextSearchProvider.cs +++ b/dotnet/src/IntegrationTests/Agents/CommonInterfaceConformance/AgentWithTextSearchProviderConformance/AgentWithTextSearchProvider.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter + using System; using System.Linq; using System.Threading; @@ -41,9 +43,7 @@ public abstract class AgentWithTextSearchProvider(Func creat public async Task TextSearchBehaviorStateIsUsedByAgentInternalAsync(string question, string expectedResult, params string[] ragResults) { // Arrange -#pragma warning disable CS0618 // ITextSearch is obsolete - Testing legacy interface var mockTextSearch = new Mock(); -#pragma warning restore CS0618 mockTextSearch.Setup(x => x.GetTextSearchResultsAsync( It.IsAny(), It.IsAny(), From 9302beb17338ad99a6bde95c958628a3686aed20 Mon Sep 17 00:00:00 2001 From: Alexander Zarei Date: Mon, 2 Mar 2026 02:15:40 -0800 Subject: [PATCH 3/5] Scope CS0618 pragmas and fix FilterClause obsolete messages - Scope file-level #pragma warning disable CS0618 to only surround code that references obsolete types, leaving LINQ-based methods clean - Fix FilterClause/EqualToFilterClause/AnyTagEqualToFilterClause [Obsolete] messages to reference VectorSearchOptions.Filter (these types live in VectorData.Abstractions, not TextSearch) --- dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs | 4 ++-- .../Concepts/Search/Bing_FunctionCallingWithTextSearch.cs | 4 ++-- dotnet/samples/Concepts/Search/Bing_TextSearch.cs | 4 ++-- dotnet/samples/Concepts/Search/Google_TextSearch.cs | 4 ++-- dotnet/samples/Concepts/Search/Tavily_TextSearch.cs | 4 ++-- .../Step3_Search_With_FunctionCalling.cs | 4 ++-- .../Data/TextSearchBehavior/TextSearchProviderOptions.cs | 4 ++-- .../FilterClauses/AnyTagEqualToFilterClause.cs | 2 +- .../FilterClauses/EqualToFilterClause.cs | 2 +- .../VectorData.Abstractions/FilterClauses/FilterClause.cs | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs b/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs index 27e036d304cf..421c8fbbbf46 100644 --- a/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs +++ b/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter - using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Data; using Microsoft.SemanticKernel.Plugins.Web.Bing; @@ -136,6 +134,7 @@ Include citations to and the date of the relevant information where it is refere )); } +#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage /// /// Show how to create a default from an and use it to /// add grounding context to a Handlebars prompt that include full web pages. @@ -186,4 +185,5 @@ Include citations to the relevant information where it is referenced in the resp promptTemplateFactory: promptTemplateFactory )); } +#pragma warning restore CS0618 } diff --git a/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs b/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs index 23cfaf88ec07..c6987b368ea0 100644 --- a/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs +++ b/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter - using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.Data; @@ -68,6 +66,7 @@ public async Task FunctionCallingWithBingTextSearchIncludingCitationsAsync() Console.WriteLine(await kernel.InvokePromptAsync("What is the Semantic Kernel? Include citations to the relevant information where it is referenced in the response.", arguments)); } +#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage /// /// Show how to create a default from an and use it with /// function calling to have the LLM include grounding context from the Microsoft Dev Blogs site in it's response. @@ -145,4 +144,5 @@ private static KernelFunction CreateSearchBySite(BingTextSearch textSearch, Text return textSearch.CreateSearch(options); } +#pragma warning restore CS0618 } diff --git a/dotnet/samples/Concepts/Search/Bing_TextSearch.cs b/dotnet/samples/Concepts/Search/Bing_TextSearch.cs index 8114194dd857..0a450cabbe3a 100644 --- a/dotnet/samples/Concepts/Search/Bing_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Bing_TextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter - using System.Text.Json; using Microsoft.SemanticKernel.Data; using Microsoft.SemanticKernel.Plugins.Web.Bing; @@ -13,6 +11,7 @@ namespace Search; /// public class Bing_TextSearch(ITestOutputHelper output) : BaseTest(output) { +#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage /// /// Show how to create a and use it to perform a text search. /// @@ -120,6 +119,7 @@ public async Task UsingBingTextSearchWithASiteFilterAsync() WriteHorizontalRule(); } } +#pragma warning restore CS0618 /// /// Show how to use enhanced LINQ filtering with BingTextSearch for type-safe searches. diff --git a/dotnet/samples/Concepts/Search/Google_TextSearch.cs b/dotnet/samples/Concepts/Search/Google_TextSearch.cs index 23561f8ef8a5..9a98af3c4544 100644 --- a/dotnet/samples/Concepts/Search/Google_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Google_TextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter - using System.Text.Json; using Google.Apis.Http; using Microsoft.SemanticKernel.Data; @@ -14,6 +12,7 @@ namespace Search; /// public class Google_TextSearch(ITestOutputHelper output) : BaseTest(output) { +#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage /// /// Show how to create a and use it to perform a text search. /// @@ -108,6 +107,7 @@ public async Task UsingGoogleTextSearchWithASiteSearchFilterAsync() Console.WriteLine(new string('-', HorizontalRuleLength)); } } +#pragma warning restore CS0618 /// /// Show how to use enhanced LINQ filtering with GoogleTextSearch including Contains, NOT, FileType, and compound AND expressions. diff --git a/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs b/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs index b91bbc8948e0..3134690cabd0 100644 --- a/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter - using System.Text.Json; using Microsoft.SemanticKernel.Data; using Microsoft.SemanticKernel.Plugins.Web.Tavily; @@ -13,6 +11,7 @@ namespace Search; /// public class Tavily_TextSearch(ITestOutputHelper output) : BaseTest(output) { +#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage /// /// Show how to create a and use it to perform a text search. /// @@ -183,6 +182,7 @@ public async Task UsingTavilyTextSearchWithAnIncludeDomainFilterAsync() WriteHorizontalRule(); } } +#pragma warning restore CS0618 /// /// Show how to use enhanced LINQ filtering with TavilyTextSearch for type-safe searches with Title.Contains() support. diff --git a/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs b/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs index e7e699794542..56cff911a80e 100644 --- a/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs +++ b/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete TextSearchOptions/TextSearchFilter - using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; @@ -72,6 +70,7 @@ public async Task FunctionCallingWithBingTextSearchIncludingCitationsAsync() Console.WriteLine(await kernel.InvokePromptAsync("What is the Semantic Kernel? Include citations to the relevant information where it is referenced in the response.", arguments)); } +#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage /// /// Show how to create a default from an and use it with /// function calling to have the LLM include grounding context from the Microsoft Dev Blogs site in it's response. @@ -162,5 +161,6 @@ private static KernelFunction CreateSearchBySite(BingTextSearch textSearch, Text return textSearch.CreateSearch(options); } +#pragma warning restore CS0618 #endregion } diff --git a/dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs b/dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs index cd1ecb58cea3..d84aa5f6bbaa 100644 --- a/dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs +++ b/dotnet/src/SemanticKernel.Core/Data/TextSearchBehavior/TextSearchProviderOptions.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete TextSearchFilter - backward compatibility - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -38,7 +36,9 @@ public int Top /// /// Gets or sets the filter expression to apply to the search query. /// +#pragma warning disable CS0618 // TextSearchFilter is obsolete - backward compatibility shim public TextSearchFilter? Filter { get; init; } +#pragma warning restore CS0618 /// /// Gets or sets the time at which the text search is performed. diff --git a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/AnyTagEqualToFilterClause.cs b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/AnyTagEqualToFilterClause.cs index 268dbe8876dd..c9419d087732 100644 --- a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/AnyTagEqualToFilterClause.cs +++ b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/AnyTagEqualToFilterClause.cs @@ -7,7 +7,7 @@ namespace Microsoft.Extensions.VectorData; /// /// Represents a filter clause that filters by checking if a field consisting of a list of values contains a specific value. /// -[Obsolete("Use LINQ expressions via TextSearchOptions.Filter instead. This type will be removed in a future version.")] +[Obsolete("Use LINQ expressions via VectorSearchOptions.Filter instead. This type will be removed in a future version.")] public sealed class AnyTagEqualToFilterClause : FilterClause { /// diff --git a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/EqualToFilterClause.cs b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/EqualToFilterClause.cs index 0ee049217b26..03fc678abc8b 100644 --- a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/EqualToFilterClause.cs +++ b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/EqualToFilterClause.cs @@ -7,7 +7,7 @@ namespace Microsoft.Extensions.VectorData; /// /// Represents a filter clause that filters using equality of a field value. /// -[Obsolete("Use LINQ expressions via TextSearchOptions.Filter instead. This type will be removed in a future version.")] +[Obsolete("Use LINQ expressions via VectorSearchOptions.Filter instead. This type will be removed in a future version.")] public sealed class EqualToFilterClause : FilterClause { /// diff --git a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/FilterClause.cs b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/FilterClause.cs index e0b3231952c1..97c8869a27ea 100644 --- a/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/FilterClause.cs +++ b/dotnet/src/VectorData/VectorData.Abstractions/FilterClauses/FilterClause.cs @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.VectorData; /// A is used to request that the underlying search service should /// filter search results based on the specified criteria. /// -[Obsolete("Use LINQ expressions via TextSearchOptions.Filter instead. This type will be removed in a future version.")] +[Obsolete("Use LINQ expressions via VectorSearchOptions.Filter instead. This type will be removed in a future version.")] public abstract class FilterClause { /// From 6cf9f47e74cac882d1032bc4402a6437b0f89833 Mon Sep 17 00:00:00 2001 From: Alexander Zarei Date: Mon, 2 Mar 2026 04:31:55 -0800 Subject: [PATCH 4/5] Narrow CS0618 pragma scoping to legacy API regions only Per reviewer feedback, replace file-level #pragma warning disable CS0618 with surgically scoped pragmas around only the code that references obsolete types (ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause, ITextEmbeddingGenerationService). - 4 connectors (Bing/Brave/Google/Tavily): scope to class declaration, legacy ITextSearch methods region, and ExtractFiltersFromLegacy - VectorStoreTextSearch: scope to class declaration, obsolete constructors, legacy methods region, legacy ExecuteVectorSearchAsync, _textEmbeddingGeneration field, and BuildFilterExpression - VectorStoreTextSearchTests: scope to legacy test methods only, leaving LINQ-based generic interface tests clean --- .../Plugins.Web/Bing/BingTextSearch.cs | 14 ++++++++++-- .../Plugins.Web/Brave/BraveTextSearch.cs | 14 ++++++++++-- .../Plugins.Web/Google/GoogleTextSearch.cs | 14 ++++++++++-- .../Plugins.Web/Tavily/TavilyTextSearch.cs | 14 ++++++++++-- .../Data/TextSearch/VectorStoreTextSearch.cs | 22 +++++++++++++++++-- .../Data/VectorStoreTextSearchTests.cs | 5 +++-- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs index d16d7831d1d1..843e24354fa6 100644 --- a/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility - using System; using System.Collections.Generic; using System.Linq; @@ -23,7 +21,9 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Bing; /// /// A Bing Text Search implementation that can be used to perform searches using the Bing Web Search API. /// +#pragma warning disable CS0618 // ITextSearch is obsolete public sealed class BingTextSearch : ITextSearch, ITextSearch +#pragma warning restore CS0618 { /// /// Create an instance of the with API key authentication. @@ -45,6 +45,10 @@ public BingTextSearch(string apiKey, BingTextSearchOptions? options = null) this._options = options; } +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause + + #region Legacy ITextSearch Implementation + /// public async Task> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { @@ -81,6 +85,10 @@ public async Task> GetSearchResultsAsync(string quer return new KernelSearchResults(this.GetResultsAsWebPageAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse)); } + #endregion + +#pragma warning restore CS0618 + /// async Task> ITextSearch.SearchAsync(string query, TextSearchOptions? searchOptions, CancellationToken cancellationToken) { @@ -556,6 +564,7 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) /// This shim converts the obsolete FilterClause-based format to the internal (FieldName, Value) list. /// It will be removed when the legacy ITextSearch interface is retired. /// +#pragma warning disable CS0618 // Obsolete TextSearchFilter, FilterClause private static List<(string FieldName, object Value)> ExtractFiltersFromLegacy(TextSearchFilter? filter) { var filters = new List<(string FieldName, object Value)>(); @@ -576,6 +585,7 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) } return filters; } +#pragma warning restore CS0618 /// /// Build a Bing API query string from pre-extracted filter key-value pairs. diff --git a/dotnet/src/Plugins/Plugins.Web/Brave/BraveTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Brave/BraveTextSearch.cs index 2f56cc4ee74a..1ecdd89197b8 100644 --- a/dotnet/src/Plugins/Plugins.Web/Brave/BraveTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Brave/BraveTextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility - using System; using System.Collections.Generic; using System.Linq; @@ -23,7 +21,9 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Brave; /// /// A Brave Text Search implementation that can be used to perform searches using the Brave Web Search API. /// +#pragma warning disable CS0618 // ITextSearch is obsolete public sealed class BraveTextSearch : ITextSearch, ITextSearch +#pragma warning restore CS0618 { /// /// Create an instance of the with API key authentication. @@ -47,6 +47,10 @@ public BraveTextSearch(string apiKey, BraveTextSearchOptions? options = null) this._resultMapper = options?.ResultMapper ?? s_defaultResultMapper; } +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause + + #region Legacy ITextSearch Implementation + /// public async Task> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = new CancellationToken()) { @@ -84,6 +88,10 @@ public async Task> GetSearchResultsAsync(string quer return new KernelSearchResults(this.GetResultsAsObjectAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse)); } + #endregion + +#pragma warning restore CS0618 + #region Generic ITextSearch Implementation /// @@ -626,6 +634,7 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) /// This shim converts the obsolete FilterClause-based format to the internal (FieldName, Value) list. /// It will be removed when the legacy ITextSearch interface is retired. /// +#pragma warning disable CS0618 // Obsolete TextSearchFilter, FilterClause private static List<(string FieldName, object Value)> ExtractFiltersFromLegacy(TextSearchFilter? filter) { var filters = new List<(string FieldName, object Value)>(); @@ -646,6 +655,7 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) } return filters; } +#pragma warning restore CS0618 /// /// Build a Brave API query string from pre-extracted filter key-value pairs. diff --git a/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs index 5785326342b7..50ac722fcc5f 100644 --- a/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility - using System; using System.Collections.Generic; using System.Linq; @@ -21,7 +19,9 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Google; /// /// A Google Text Search implementation that can be used to perform searches using the Google Web Search API. /// +#pragma warning disable CS0618 // ITextSearch is obsolete public sealed class GoogleTextSearch : ITextSearch, ITextSearch, IDisposable +#pragma warning restore CS0618 { /// /// Initializes a new instance of the class. @@ -59,6 +59,10 @@ public GoogleTextSearch( this._options = options; } +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause + + #region Legacy ITextSearch Implementation + /// public async Task> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { @@ -95,6 +99,10 @@ public async Task> SearchAsync(string query, TextSea return new KernelSearchResults(this.GetResultsAsStringAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse)); } + #endregion + +#pragma warning restore CS0618 + #region ITextSearch Implementation /// @@ -382,6 +390,7 @@ public void Dispose() /// This shim converts the obsolete FilterClause-based format to the internal (FieldName, Value) list. /// It will be removed when the legacy ITextSearch interface is retired. /// +#pragma warning disable CS0618 // Obsolete TextSearchFilter, FilterClause private static List<(string FieldName, object Value)> ExtractFiltersFromLegacy(TextSearchFilter? filter) { var filters = new List<(string FieldName, object Value)>(); @@ -402,6 +411,7 @@ public void Dispose() } return filters; } +#pragma warning restore CS0618 /// /// Apply pre-extracted filter key-value pairs to the Google search request. diff --git a/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs index 9933ec7ce160..41fd5cd35df1 100644 --- a/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -23,7 +21,9 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Tavily; /// /// A Tavily Text Search implementation that can be used to perform searches using the Tavily Web Search API. /// +#pragma warning disable CS0618 // ITextSearch is obsolete public sealed class TavilyTextSearch : ITextSearch, ITextSearch +#pragma warning restore CS0618 { /// /// Create an instance of the with API key authentication. @@ -45,6 +45,10 @@ public TavilyTextSearch(string apiKey, TavilyTextSearchOptions? options = null) this._resultMapper = options?.ResultMapper ?? s_defaultResultMapper; } +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause + + #region Legacy ITextSearch Implementation + /// public async Task> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { @@ -81,6 +85,10 @@ public async Task> GetSearchResultsAsync(string quer return new KernelSearchResults(this.GetSearchResultsAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse)); } + #endregion + +#pragma warning restore CS0618 + #region Generic ITextSearch Implementation /// @@ -632,6 +640,7 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) /// This shim converts the obsolete FilterClause-based format to the internal (FieldName, Value) list. /// It will be removed when the legacy ITextSearch interface is retired. /// +#pragma warning disable CS0618 // Obsolete TextSearchFilter, FilterClause private static List<(string FieldName, object Value)> ExtractFiltersFromLegacy(TextSearchFilter? filter) { var filters = new List<(string FieldName, object Value)>(); @@ -652,6 +661,7 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) } return filters; } +#pragma warning restore CS0618 /// /// Build a Tavily API request from pre-extracted filter key-value pairs. diff --git a/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs b/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs index bfd4fb493f49..15d676009cec 100644 --- a/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs +++ b/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions, TextSearchFilter, FilterClause - backward compatibility - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -21,7 +19,9 @@ namespace Microsoft.SemanticKernel.Data; /// A Vector Store Text Search implementation that can be used to perform searches using a . /// [Experimental("SKEXP0001")] +#pragma warning disable CS0618 // ITextSearch is obsolete public sealed class VectorStoreTextSearch<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TRecord> : ITextSearch, ITextSearch +#pragma warning restore CS0618 #pragma warning restore CA1711 // Identifiers should not have incorrect suffix { /// @@ -74,6 +74,8 @@ public VectorStoreTextSearch( { } +#pragma warning disable CS0618 // Obsolete ITextEmbeddingGenerationService constructors + /// /// Create an instance of the with the /// provided for performing searches and @@ -128,6 +130,8 @@ public VectorStoreTextSearch( this._resultMapper = resultMapper ?? this.CreateTextSearchResultMapper(); } +#pragma warning restore CS0618 + /// /// Create an instance of the with the /// provided for performing searches and @@ -173,6 +177,10 @@ public VectorStoreTextSearch( this._resultMapper = resultMapper ?? this.CreateTextSearchResultMapper(); } +#pragma warning disable CS0618 // Obsolete ITextSearch, TextSearchOptions + + #region Legacy ITextSearch Implementation + /// public Task> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { @@ -197,6 +205,10 @@ public Task> GetSearchResultsAsync(string query, Tex return Task.FromResult(new KernelSearchResults(this.GetResultsAsRecordAsync(searchResponse, cancellationToken))); } + #endregion + +#pragma warning restore CS0618 + /// Task> ITextSearch.SearchAsync(string query, TextSearchOptions? searchOptions, CancellationToken cancellationToken) { @@ -222,8 +234,10 @@ Task> ITextSearch.GetSearchResultsAsync(st } #region private +#pragma warning disable CS0618 // Obsolete ITextEmbeddingGenerationService [Obsolete("This property is obsolete.")] private readonly ITextEmbeddingGenerationService? _textEmbeddingGeneration; +#pragma warning restore CS0618 private readonly IVectorSearchable? _vectorSearchable; private readonly ITextSearchStringMapper _stringMapper; private readonly ITextSearchResultMapper _resultMapper; @@ -270,6 +284,7 @@ private TextSearchStringMapper CreateTextSearchStringMapper() }); } +#pragma warning disable CS0618 // Obsolete TextSearchOptions, FilterClause /// /// Execute a vector search and return the results using legacy filtering for backward compatibility. /// Converts legacy values to a LINQ expression tree for the modern @@ -295,6 +310,7 @@ private async IAsyncEnumerable> ExecuteVectorSearchA yield return result; } } +#pragma warning restore CS0618 /// /// Execute a vector search and return the results using modern LINQ filtering. @@ -432,6 +448,7 @@ private async IAsyncEnumerable GetResultsAsStringAsync(IAsyncEnumerable< } } +#pragma warning disable CS0618 // Obsolete FilterClause, EqualToFilterClause /// /// Converts a collection of legacy instances to a LINQ expression tree. /// @@ -488,6 +505,7 @@ private async IAsyncEnumerable GetResultsAsStringAsync(IAsyncEnumerable< return Expression.Lambda>(combined!, parameter); } +#pragma warning restore CS0618 #endregion } diff --git a/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs b/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs index f17196cdb70a..c948240a5646 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CS0618 // Testing obsolete TextSearchFilter backward compatibility - using System; using System.Linq; using System.Threading.Tasks; @@ -14,6 +12,7 @@ namespace SemanticKernel.UnitTests.Data; public class VectorStoreTextSearchTests : VectorStoreTextSearchTestBase { +#pragma warning disable CS0618 // Testing obsolete TextSearchFilter backward compatibility [Fact] public void CanCreateVectorStoreTextSearchWithEmbeddingGenerationService() { @@ -207,6 +206,8 @@ public async Task CanFilterGetSearchResultsWithVectorizedSearchAsync() Assert.Equal("Odd", result2?.Tag); } +#pragma warning restore CS0618 + #region Generic Interface Tests (ITextSearch) [Fact] From 0c9e21b0ffaff2c01d3628cbc644d2a0e2ead636 Mon Sep 17 00:00:00 2001 From: Alexander Zarei Date: Mon, 2 Mar 2026 08:48:26 -0800 Subject: [PATCH 5/5] Add CS0618 pragma coverage for IEmbeddingGenerator constructor and ExecuteVectorSearchCoreAsync --- .../Data/TextSearch/VectorStoreTextSearch.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs b/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs index 15d676009cec..c83bdbcbddb9 100644 --- a/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs +++ b/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs @@ -59,6 +59,7 @@ public VectorStoreTextSearch( /// instance that can map a TRecord to a /// instance that can map a TRecord to a /// Options used to construct an instance of +#pragma warning disable CS0618 // Chains to obsolete ITextEmbeddingGenerationService constructor public VectorStoreTextSearch( IVectorSearchable vectorSearchable, IEmbeddingGenerator> embeddingGenerator, @@ -73,6 +74,7 @@ public VectorStoreTextSearch( options) { } +#pragma warning restore CS0618 #pragma warning disable CS0618 // Obsolete ITextEmbeddingGenerationService constructors @@ -342,6 +344,7 @@ private async IAsyncEnumerable> ExecuteVectorSearchA /// The to monitor for cancellation requests. private async IAsyncEnumerable> ExecuteVectorSearchCoreAsync(string query, VectorSearchOptions vectorSearchOptions, int top, [EnumeratorCancellation] CancellationToken cancellationToken) { +#pragma warning disable CS0618 // Obsolete _textEmbeddingGeneration backward compatibility if (this._textEmbeddingGeneration is not null) { var vectorizedQuery = await this._textEmbeddingGeneration!.GenerateEmbeddingAsync(query, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -353,6 +356,7 @@ private async IAsyncEnumerable> ExecuteVectorSearchC yield break; } +#pragma warning restore CS0618 await foreach (var result in this._vectorSearchable!.SearchAsync(query, top, vectorSearchOptions, cancellationToken).WithCancellation(cancellationToken).ConfigureAwait(false)) {