33using System ;
44using System . Collections . Generic ;
55using System . Linq ;
6+ using System . Linq . Expressions ;
67using System . Net . Http ;
78using System . Runtime . CompilerServices ;
89using 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