Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Plugins.Web.Bing;
Expand Down Expand Up @@ -133,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
/// <summary>
/// Show how to create a default <see cref="KernelPlugin"/> from an <see cref="ITextSearch"/> and use it to
/// add grounding context to a Handlebars prompt that include full web pages.
Expand Down Expand Up @@ -183,4 +185,5 @@ Include citations to the relevant information where it is referenced in the resp
promptTemplateFactory: promptTemplateFactory
));
}
#pragma warning restore CS0618
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,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
/// <summary>
/// Show how to create a default <see cref="KernelPlugin"/> from an <see cref="BingTextSearch"/> and use it with
/// function calling to have the LLM include grounding context from the Microsoft Dev Blogs site in it's response.
Expand Down Expand Up @@ -143,4 +144,5 @@ private static KernelFunction CreateSearchBySite(BingTextSearch textSearch, Text

return textSearch.CreateSearch(options);
}
#pragma warning restore CS0618
}
2 changes: 2 additions & 0 deletions dotnet/samples/Concepts/Search/Bing_TextSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Search;
/// </summary>
public class Bing_TextSearch(ITestOutputHelper output) : BaseTest(output)
{
#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage
/// <summary>
/// Show how to create a <see cref="BingTextSearch"/> and use it to perform a text search.
/// </summary>
Expand Down Expand Up @@ -118,6 +119,7 @@ public async Task UsingBingTextSearchWithASiteFilterAsync()
WriteHorizontalRule();
}
}
#pragma warning restore CS0618

/// <summary>
/// Show how to use enhanced LINQ filtering with BingTextSearch for type-safe searches.
Expand Down
2 changes: 2 additions & 0 deletions dotnet/samples/Concepts/Search/Google_TextSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Search;
/// </summary>
public class Google_TextSearch(ITestOutputHelper output) : BaseTest(output)
{
#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage
/// <summary>
/// Show how to create a <see cref="GoogleTextSearch"/> and use it to perform a text search.
/// </summary>
Expand Down Expand Up @@ -106,6 +107,7 @@ public async Task UsingGoogleTextSearchWithASiteSearchFilterAsync()
Console.WriteLine(new string('-', HorizontalRuleLength));
}
}
#pragma warning restore CS0618

/// <summary>
/// Show how to use enhanced LINQ filtering with GoogleTextSearch including Contains, NOT, FileType, and compound AND expressions.
Expand Down
2 changes: 2 additions & 0 deletions dotnet/samples/Concepts/Search/Tavily_TextSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Search;
/// </summary>
public class Tavily_TextSearch(ITestOutputHelper output) : BaseTest(output)
{
#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage
/// <summary>
/// Show how to create a <see cref="TavilyTextSearch"/> and use it to perform a text search.
/// </summary>
Expand Down Expand Up @@ -181,6 +182,7 @@ public async Task UsingTavilyTextSearchWithAnIncludeDomainFilterAsync()
WriteHorizontalRule();
}
}
#pragma warning restore CS0618

/// <summary>
/// Show how to use enhanced LINQ filtering with TavilyTextSearch for type-safe searches with Title.Contains() support.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
Expand Down Expand Up @@ -69,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
/// <summary>
/// Show how to create a default <see cref="KernelPlugin"/> from an <see cref="BingTextSearch"/> and use it with
/// function calling to have the LLM include grounding context from the Microsoft Dev Blogs site in it's response.
Expand Down Expand Up @@ -159,5 +161,6 @@ private static KernelFunction CreateSearchBySite(BingTextSearch textSearch, Text

return textSearch.CreateSearch(options);
}
#pragma warning restore CS0618
#endregion
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -41,9 +43,7 @@ public abstract class AgentWithTextSearchProvider<TFixture>(Func<TFixture> 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<ITextSearch>();
#pragma warning restore CS0618
mockTextSearch.Setup(x => x.GetTextSearchResultsAsync(
It.IsAny<string>(),
It.IsAny<TextSearchOptions>(),
Expand Down
102 changes: 36 additions & 66 deletions dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Non-generic ITextSearch is obsolete - provides backward compatibility during Phase 2 LINQ migration

using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -23,7 +21,7 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Bing;
/// <summary>
/// A Bing Text Search implementation that can be used to perform searches using the Bing Web Search API.
/// </summary>
#pragma warning disable CS0618 // ITextSearch is obsolete - this class provides backward compatibility
#pragma warning disable CS0618 // ITextSearch is obsolete
public sealed class BingTextSearch : ITextSearch, ITextSearch<BingWebPage>
#pragma warning restore CS0618
{
Expand All @@ -47,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

/// <inheritdoc/>
public async Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -83,6 +85,10 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer
return new KernelSearchResults<object>(this.GetResultsAsWebPageAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse));
}

#endregion

#pragma warning restore CS0618

/// <inheritdoc/>
async Task<KernelSearchResults<string>> ITextSearch<BingWebPage>.SearchAsync(string query, TextSearchOptions<BingWebPage>? searchOptions, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -172,67 +178,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. " +
Expand Down Expand Up @@ -360,32 +347,13 @@ private static void ProcessContainsExpression(MethodCallExpression methodExpr, L
}
}

/// <summary>
/// Determines if a MethodCallExpression is a MemoryExtensions.Contains call (C# 14 "first-class spans" feature).
/// </summary>
/// <param name="methodExpr">The method call expression to check.</param>
/// <returns>True if this is a MemoryExtensions.Contains call with supported parameters; otherwise false.</returns>
private static bool IsMemoryExtensionsContains(MethodCallExpression methodExpr)
{
// MemoryExtensions.Contains has 2-3 parameters:
// - Contains<T>(ReadOnlySpan<T> span, T value)
// - Contains<T>(ReadOnlySpan<T> span, T value, IEqualityComparer<T>? 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 }));
}

/// <summary>
/// Maps BingWebPage property names to Bing API filter field names for equality operations.
/// </summary>
/// <param name="propertyName">The BingWebPage property name.</param>
/// <returns>The corresponding Bing API filter name, or null if not mappable.</returns>
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
Expand All @@ -401,16 +369,14 @@ private static bool IsMemoryExtensionsContains(MethodCallExpression methodExpr)

_ => null // Property not mappable to Bing filters
};
}

/// <summary>
/// Maps BingWebPage property names to Bing API advanced search operators for Contains operations.
/// </summary>
/// <param name="propertyName">The BingWebPage property name.</param>
/// <returns>The corresponding Bing advanced search operator, or null if not mappable.</returns>
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
Expand All @@ -420,7 +386,6 @@ private static bool IsMemoryExtensionsContains(MethodCallExpression methodExpr)

_ => null // Property not mappable to Contains-style filters
};
}

/// <summary>
/// Execute a Bing search query and return the results.
Expand Down Expand Up @@ -594,12 +559,12 @@ public TextSearchResult MapFromResultToTextSearchResult(object result)
}
}

#pragma warning disable CS0618 // FilterClause is obsolete - backward compatibility shim for legacy ITextSearch
/// <summary>
/// Extracts filter key-value pairs from a legacy <see cref="TextSearchFilter"/>.
/// 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.
/// </summary>
#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)>();
Expand All @@ -611,6 +576,11 @@ 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;
Expand Down
Loading
Loading