Skip to content

Commit 5d56fac

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 c2c783b commit 5d56fac

File tree

1 file changed

+97
-1
lines changed

1 file changed

+97
-1
lines changed

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;
@@ -20,7 +21,7 @@ namespace Microsoft.SemanticKernel.Plugins.Web.Bing;
2021
/// <summary>
2122
/// A Bing Text Search implementation that can be used to perform searches using the Bing Web Search API.
2223
/// </summary>
23-
public sealed class BingTextSearch : ITextSearch
24+
public sealed class BingTextSearch : ITextSearch, ITextSearch<BingWebPage>
2425
{
2526
/// <summary>
2627
/// Create an instance of the <see cref="BingTextSearch"/> with API key authentication.
@@ -74,6 +75,27 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer
7475
return new KernelSearchResults<object>(this.GetResultsAsWebPageAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse));
7576
}
7677

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

79101
private readonly ILogger _logger;
@@ -92,6 +114,80 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer
92114

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

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

0 commit comments

Comments
 (0)