Skip to content

Commit e77a9bb

Browse files
committed
feat: Add ITextSearch<BingWebPage> generic interface support to BingTextSearch
Implement ITextSearch<BingWebPage> alongside existing ITextSearch interface Add LINQ expression conversion logic with property mapping to Bing API parameters Support type-safe filtering with BingWebPage properties Provide graceful degradation for unsupported LINQ expressions Maintain 100% backward compatibility with existing legacy interface Addresses #10456 Part of PR 3/6 in structured modernization of ITextSearch interfaces
1 parent ebd579d commit e77a9bb

1 file changed

Lines changed: 97 additions & 1 deletion

File tree

dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Collections.Generic;
55
using System.Linq;
6+
using System.Linq.Expressions;
67
using System.Net.Http;
78
using System.Runtime.CompilerServices;
89
using System.Text;
@@ -21,7 +22,7 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Bing;
2122
/// A Bing Text Search implementation that can be used to perform searches using the Bing Web Search API.
2223
/// </summary>
2324
#pragma warning disable CS0618 // ITextSearch is obsolete - this class provides backward compatibility
24-
public sealed class BingTextSearch : ITextSearch
25+
public sealed class BingTextSearch : ITextSearch, ITextSearch<BingWebPage>
2526
#pragma warning restore CS0618
2627
{
2728
/// <summary>
@@ -76,6 +77,27 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer
7677
return new KernelSearchResults<object>(this.GetResultsAsWebPageAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse));
7778
}
7879

80+
/// <inheritdoc/>
81+
Task<KernelSearchResults<string>> ITextSearch<BingWebPage>.SearchAsync(string query, TextSearchOptions<BingWebPage>? searchOptions, CancellationToken cancellationToken)
82+
{
83+
var legacyOptions = searchOptions != null ? ConvertToLegacyOptions(searchOptions) : new TextSearchOptions();
84+
return this.SearchAsync(query, legacyOptions, cancellationToken);
85+
}
86+
87+
/// <inheritdoc/>
88+
Task<KernelSearchResults<TextSearchResult>> ITextSearch<BingWebPage>.GetTextSearchResultsAsync(string query, TextSearchOptions<BingWebPage>? searchOptions, CancellationToken cancellationToken)
89+
{
90+
var legacyOptions = searchOptions != null ? ConvertToLegacyOptions(searchOptions) : new TextSearchOptions();
91+
return this.GetTextSearchResultsAsync(query, legacyOptions, cancellationToken);
92+
}
93+
94+
/// <inheritdoc/>
95+
Task<KernelSearchResults<object>> ITextSearch<BingWebPage>.GetSearchResultsAsync(string query, TextSearchOptions<BingWebPage>? searchOptions, CancellationToken cancellationToken)
96+
{
97+
var legacyOptions = searchOptions != null ? ConvertToLegacyOptions(searchOptions) : new TextSearchOptions();
98+
return this.GetSearchResultsAsync(query, legacyOptions, cancellationToken);
99+
}
100+
79101
#region private
80102

81103
private readonly ILogger _logger;
@@ -94,6 +116,80 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer
94116

95117
private const string DefaultUri = "https://api.bing.microsoft.com/v7.0/search";
96118

119+
/// <summary>
120+
/// Converts generic TextSearchOptions with LINQ filtering to legacy TextSearchOptions.
121+
/// Attempts to translate simple LINQ expressions to Bing API filters where possible.
122+
/// </summary>
123+
/// <param name="genericOptions">The generic search options with LINQ filtering.</param>
124+
/// <returns>Legacy TextSearchOptions with equivalent filtering, or null if no conversion possible.</returns>
125+
private static TextSearchOptions ConvertToLegacyOptions(TextSearchOptions<BingWebPage> genericOptions)
126+
{
127+
return new TextSearchOptions
128+
{
129+
Top = genericOptions.Top,
130+
Skip = genericOptions.Skip,
131+
Filter = genericOptions.Filter != null ? ConvertLinqExpressionToBingFilter(genericOptions.Filter) : null
132+
};
133+
}
134+
135+
/// <summary>
136+
/// Converts a LINQ expression to a TextSearchFilter compatible with Bing API.
137+
/// Only supports simple property equality expressions that map to Bing's filter capabilities.
138+
/// </summary>
139+
/// <param name="linqExpression">The LINQ expression to convert.</param>
140+
/// <returns>A TextSearchFilter with equivalent filtering.</returns>
141+
/// <exception cref="NotSupportedException">Thrown when the expression cannot be converted to Bing filters.</exception>
142+
private static TextSearchFilter ConvertLinqExpressionToBingFilter<TRecord>(Expression<Func<TRecord, bool>> linqExpression)
143+
{
144+
if (linqExpression.Body is BinaryExpression binaryExpr && binaryExpr.NodeType == ExpressionType.Equal)
145+
{
146+
// Handle simple equality: record.PropertyName == "value"
147+
if (binaryExpr.Left is MemberExpression memberExpr && binaryExpr.Right is ConstantExpression constExpr)
148+
{
149+
string propertyName = memberExpr.Member.Name;
150+
object? value = constExpr.Value;
151+
152+
// Map BingWebPage properties to Bing API filter names
153+
string? bingFilterName = MapPropertyToBingFilter(propertyName);
154+
if (bingFilterName != null && value != null)
155+
{
156+
return new TextSearchFilter().Equality(bingFilterName, value);
157+
}
158+
}
159+
}
160+
161+
throw new NotSupportedException(
162+
"LINQ expression '" + linqExpression + "' cannot be converted to Bing API filters. " +
163+
"Only simple equality expressions like 'page => page.Language == \"en\"' are supported, " +
164+
"and only for properties that map to Bing API parameters: " +
165+
string.Join(", ", s_queryParameters.Concat(s_advancedSearchKeywords)));
166+
}
167+
168+
/// <summary>
169+
/// Maps BingWebPage property names to Bing API filter field names.
170+
/// </summary>
171+
/// <param name="propertyName">The BingWebPage property name.</param>
172+
/// <returns>The corresponding Bing API filter name, or null if not mappable.</returns>
173+
private static string? MapPropertyToBingFilter(string propertyName)
174+
{
175+
return propertyName.ToUpperInvariant() switch
176+
{
177+
// Map BingWebPage properties to Bing API equivalents
178+
"LANGUAGE" => "language", // Maps to advanced search
179+
"URL" => "url", // Maps to advanced search
180+
"DISPLAYURL" => "site", // Maps to site: search
181+
"NAME" => "intitle", // Maps to title search
182+
"SNIPPET" => "inbody", // Maps to body content search
183+
184+
// Direct API parameters (if we ever extend BingWebPage with metadata)
185+
"MKT" => "mkt", // Market/locale
186+
"FRESHNESS" => "freshness", // Date freshness
187+
"SAFESEARCH" => "safeSearch", // Safe search setting
188+
189+
_ => null // Property not mappable to Bing filters
190+
};
191+
}
192+
97193
/// <summary>
98194
/// Execute a Bing search query and return the results.
99195
/// </summary>

0 commit comments

Comments
 (0)