Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,24 @@ namespace GettingStartedWithTextSearch;
/// </summary>
public class InMemoryVectorStoreFixture : IAsyncLifetime
{
/// <summary>
/// Gets the embedding generator used for creating vector embeddings.
/// </summary>
public IEmbeddingGenerator<string, Embedding<float>> EmbeddingGenerator { get; private set; }

/// <summary>
/// Gets the in-memory vector store instance.
/// </summary>
public InMemoryVectorStore InMemoryVectorStore { get; private set; }

/// <summary>
/// Gets the vector store record collection for data models.
/// </summary>
public VectorStoreCollection<Guid, DataModel> VectorStoreRecordCollection { get; private set; }

/// <summary>
/// Gets the name of the collection used for storing records.
/// </summary>
public string CollectionName => "records";

/// <summary>
Expand Down Expand Up @@ -138,21 +150,36 @@ private async Task<VectorStoreCollection<TKey, TRecord>> CreateCollectionFromLis
/// </remarks>
public sealed class DataModel
{
/// <summary>
/// Gets or sets the unique identifier for this record.
/// </summary>
[VectorStoreKey]
[TextSearchResultName]
public Guid Key { get; init; }

/// <summary>
/// Gets or sets the text content of this record.
/// </summary>
[VectorStoreData]
[TextSearchResultValue]
public string Text { get; init; }

/// <summary>
/// Gets or sets the link associated with this record.
/// </summary>
[VectorStoreData]
[TextSearchResultLink]
public string Link { get; init; }

/// <summary>
/// Gets or sets the tag for categorizing this record.
/// </summary>
[VectorStoreData(IsIndexed = true)]
public required string Tag { get; init; }

/// <summary>
/// Gets the embedding representation of the text content.
/// </summary>
[VectorStoreVector(1536)]
public string Embedding => Text;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,79 @@
}
}

/// <summary>
/// Show how to use <see cref="BingTextSearch"/> with the new generic <see cref="ITextSearch{TRecord}"/>
/// interface and LINQ filtering for type-safe searches.
/// </summary>
[Fact]
public async Task BingSearchWithLinqFilteringAsync()
{
// Create a BingTextSearch instance
var bingSearch = new BingTextSearch(apiKey: TestConfiguration.Bing.ApiKey);

// Use the new generic interface for type-safe searches
ITextSearch<BingWebPage> textSearch = bingSearch;

Check failure on line 67 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

Cannot implicitly convert type 'Microsoft.SemanticKernel.Plugins.Web.Bing.BingTextSearch' to 'Microsoft.SemanticKernel.Data.ITextSearch<Microsoft.SemanticKernel.Plugins.Web.Bing.BingWebPage>'

Check failure on line 67 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

Cannot implicitly convert type 'Microsoft.SemanticKernel.Plugins.Web.Bing.BingTextSearch' to 'Microsoft.SemanticKernel.Data.ITextSearch<Microsoft.SemanticKernel.Plugins.Web.Bing.BingWebPage>'

Check failure on line 67 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

Cannot implicitly convert type 'Microsoft.SemanticKernel.Plugins.Web.Bing.BingTextSearch' to 'Microsoft.SemanticKernel.Data.ITextSearch<Microsoft.SemanticKernel.Plugins.Web.Bing.BingWebPage>'

Check failure on line 67 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

Cannot implicitly convert type 'Microsoft.SemanticKernel.Plugins.Web.Bing.BingTextSearch' to 'Microsoft.SemanticKernel.Data.ITextSearch<Microsoft.SemanticKernel.Plugins.Web.Bing.BingWebPage>'

Check failure on line 67 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

Cannot implicitly convert type 'Microsoft.SemanticKernel.Plugins.Web.Bing.BingTextSearch' to 'Microsoft.SemanticKernel.Data.ITextSearch<Microsoft.SemanticKernel.Plugins.Web.Bing.BingWebPage>'

Check failure on line 67 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

Cannot implicitly convert type 'Microsoft.SemanticKernel.Plugins.Web.Bing.BingTextSearch' to 'Microsoft.SemanticKernel.Data.ITextSearch<Microsoft.SemanticKernel.Plugins.Web.Bing.BingWebPage>'

var query = "What is the Semantic Kernel?";

// Use LINQ filtering for type-safe search with compile-time validation
var options = new TextSearchOptions<BingWebPage>
{
Top = 4,
Filter = page => page.Language == "en" && page.IsFamilyFriendly == true
};

// Search and return strongly-typed results
Console.WriteLine("\n--- Bing Search with LINQ Filtering ---\n");
KernelSearchResults<BingWebPage> searchResults = await textSearch.GetSearchResultsAsync(query, options);
await foreach (BingWebPage page in searchResults.Results)
{
Console.WriteLine($"Name: {page.Name}");
Console.WriteLine($"Snippet: {page.Snippet}");
Console.WriteLine($"Url: {page.Url}");
Console.WriteLine($"Language: {page.Language}");
Console.WriteLine($"Family Friendly: {page.IsFamilyFriendly}");
Console.WriteLine("---");
}
}

/// <summary>
/// Show how to use <see cref="GoogleTextSearch"/> with the new generic <see cref="ITextSearch{TRecord}"/>
/// interface and LINQ filtering for type-safe searches.
/// </summary>
[Fact]
public async Task GoogleSearchWithLinqFilteringAsync()
{
// Create a GoogleTextSearch instance
var googleSearch = new GoogleTextSearch(
searchEngineId: TestConfiguration.Google.SearchEngineId,
apiKey: TestConfiguration.Google.ApiKey);

// Use the new generic interface for type-safe searches
ITextSearch<GoogleWebPage> textSearch = googleSearch;

Check failure on line 105 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 105 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 105 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 105 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 105 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 105 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

var query = "What is the Semantic Kernel?";

// Use LINQ filtering for type-safe search with compile-time validation
var options = new TextSearchOptions<GoogleWebPage>

Check failure on line 110 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 110 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 110 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 110 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 110 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 110 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)
{
Top = 4,
Filter = page => page.Title.Contains("Semantic") && page.DisplayLink.EndsWith(".com")
};

// Search and return strongly-typed results
Console.WriteLine("\n--- Google Search with LINQ Filtering ---\n");
KernelSearchResults<GoogleWebPage> searchResults = await textSearch.GetSearchResultsAsync(query, options);

Check failure on line 118 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 118 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 118 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 118 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 118 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 118 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)
await foreach (GoogleWebPage page in searchResults.Results)

Check failure on line 119 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 119 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 119 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 119 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 119 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 119 in dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

The type or namespace name 'GoogleWebPage' could not be found (are you missing a using directive or an assembly reference?)
{
Console.WriteLine($"Title: {page.Title}");
Console.WriteLine($"Snippet: {page.Snippet}");
Console.WriteLine($"Link: {page.Link}");
Console.WriteLine($"Display Link: {page.DisplayLink}");
Console.WriteLine("---");
}
}

/// <summary>
/// Show how to create a <see cref="BingTextSearch"/> and use it to perform a search
/// and return results as a collection of <see cref="BingWebPage"/> instances.
Expand Down Expand Up @@ -86,7 +159,7 @@
}
else
{
Console.WriteLine("\n——— Google Web Page Results ———\n");
Console.WriteLine("\n��� Google Web Page Results ���\n");
await foreach (Google.Apis.CustomSearchAPI.v1.Data.Result result in objectResults.Results)
{
Console.WriteLine($"Title: {result.Title}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(
CancellationToken cancellationToken = default);

/// <summary>
/// Perform a search for content related to the specified query and return <see cref="object"/> values representing the search results.
/// Perform a search for content related to the specified query and return strongly-typed <typeparamref name="TRecord"/> values representing the search results.
/// </summary>
/// <param name="query">What to search for.</param>
/// <param name="searchOptions">Options used when executing a text search.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
Task<KernelSearchResults<object>> GetSearchResultsAsync(
Task<KernelSearchResults<TRecord>> GetSearchResultsAsync(
string query,
TextSearchOptions<TRecord>? searchOptions = null,
CancellationToken cancellationToken = default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,11 @@ Task<KernelSearchResults<TextSearchResult>> ITextSearch<TRecord>.GetTextSearchRe
}

/// <inheritdoc/>
Task<KernelSearchResults<object>> ITextSearch<TRecord>.GetSearchResultsAsync(string query, TextSearchOptions<TRecord>? searchOptions, CancellationToken cancellationToken)
Task<KernelSearchResults<TRecord>> ITextSearch<TRecord>.GetSearchResultsAsync(string query, TextSearchOptions<TRecord>? searchOptions, CancellationToken cancellationToken)
{
var searchResponse = this.ExecuteVectorSearchAsync(query, searchOptions, cancellationToken);

return Task.FromResult(new KernelSearchResults<object>(this.GetResultsAsRecordAsync(searchResponse, cancellationToken)));
return Task.FromResult(new KernelSearchResults<TRecord>(this.GetResultsAsTRecordAsync(searchResponse, cancellationToken)));
}

#region private
Expand Down Expand Up @@ -367,6 +367,28 @@ private async IAsyncEnumerable<object> GetResultsAsRecordAsync(IAsyncEnumerable<
}
}

/// <summary>
/// Return the search results as strongly-typed <typeparamref name="TRecord"/> instances.
/// </summary>
/// <param name="searchResponse">Response containing the records matching the query.</param>
/// <param name="cancellationToken">Cancellation token</param>
private async IAsyncEnumerable<TRecord> GetResultsAsTRecordAsync(IAsyncEnumerable<VectorSearchResult<TRecord>>? searchResponse, [EnumeratorCancellation] CancellationToken cancellationToken)
{
if (searchResponse is null)
{
yield break;
}

await foreach (var result in searchResponse.WithCancellation(cancellationToken).ConfigureAwait(false))
{
if (result.Record is not null)
{
yield return result.Record;
await Task.Yield();
}
}
}

/// <summary>
/// Return the search results as instances of <see cref="TextSearchResult"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@ public async Task CanGetSearchResultAsync()
{
// Arrange.
var sut = await CreateVectorStoreTextSearchAsync();
ITextSearch<DataModel> typeSafeInterface = sut;

// Act.
KernelSearchResults<object> searchResults = await sut.GetSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 2, Skip = 0 });
KernelSearchResults<DataModel> searchResults = await typeSafeInterface.GetSearchResultsAsync("What is the Semantic Kernel?", new TextSearchOptions<DataModel> { Top = 2, Skip = 0 });
var results = await searchResults.Results.ToListAsync();

Assert.Equal(2, results.Count);
Assert.All(results, result => Assert.IsType<DataModel>(result));
}

[Fact]
Expand Down Expand Up @@ -117,12 +119,14 @@ public async Task CanGetSearchResultsWithEmbeddingGeneratorAsync()
{
// Arrange.
var sut = await CreateVectorStoreTextSearchWithEmbeddingGeneratorAsync();
ITextSearch<DataModelWithRawEmbedding> typeSafeInterface = sut;

// Act.
KernelSearchResults<object> searchResults = await sut.GetSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 2, Skip = 0 });
KernelSearchResults<DataModelWithRawEmbedding> searchResults = await typeSafeInterface.GetSearchResultsAsync("What is the Semantic Kernel?", new TextSearchOptions<DataModelWithRawEmbedding> { Top = 2, Skip = 0 });
var results = await searchResults.Results.ToListAsync();

Assert.Equal(2, results.Count);
Assert.All(results, result => Assert.IsType<DataModelWithRawEmbedding>(result));
}

#pragma warning disable CS0618 // VectorStoreTextSearch with ITextEmbeddingGenerationService is obsolete
Expand Down Expand Up @@ -270,17 +274,16 @@ public async Task LinqGetSearchResultsAsync()
Filter = r => r.Tag == "Even"
};

KernelSearchResults<object> searchResults = await typeSafeInterface.GetSearchResultsAsync(
KernelSearchResults<DataModel> searchResults = await typeSafeInterface.GetSearchResultsAsync(
"What is the Semantic Kernel?",
searchOptions);
var results = await searchResults.Results.ToListAsync();

// Assert - Results should be DataModel objects with Tag == "Even"
// Assert - Results should be strongly-typed DataModel objects with Tag == "Even"
Assert.NotEmpty(results);
Assert.All(results, result =>
{
var dataModel = Assert.IsType<DataModel>(result);
Assert.Equal("Even", dataModel.Tag);
Assert.Equal("Even", result.Tag); // Direct property access - no cast needed!
});
}

Expand Down
Loading