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 ;
@@ -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