Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net: Text Search Service ADR #6012

Open
wants to merge 22 commits into
base: feature-text-search-service
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
76ac061
ADR for the ITextSearchService abstraction
markwallace-microsoft Apr 8, 2024
9ef2b6e
Merge remote-tracking branch 'upstream/main' into users/markwallace/t…
markwallace-microsoft Apr 15, 2024
5f6d552
Add implementations for Google and Bing
markwallace-microsoft Apr 15, 2024
f1d1286
Add basic Bing text search service
markwallace-microsoft Apr 15, 2024
e8bdf81
Add decision drivers to the ADR
markwallace-microsoft Apr 26, 2024
7ee05e3
Google search service
markwallace-microsoft Apr 27, 2024
280d969
Try to eliminate KernelSearchResult
markwallace-microsoft Apr 29, 2024
f167fe3
Eliminated KernelSearchResult
markwallace-microsoft Apr 29, 2024
cb5676e
Update samples
markwallace-microsoft Apr 29, 2024
5a73ae9
Start work on TextSearchPlugin
markwallace-microsoft Apr 30, 2024
138a6cd
Update ADR
markwallace-microsoft May 1, 2024
837284f
Updates to ADR
markwallace-microsoft May 1, 2024
6c38fae
ADR updates and also update the plugin sample
markwallace-microsoft May 2, 2024
2bae9e8
Fix spelling mistakes
markwallace-microsoft May 2, 2024
a63a601
Merge remote-tracking branch 'upstream/feature-text-search-service' i…
markwallace-microsoft May 3, 2024
50e2711
Merge remote-tracking branch 'upstream/feature-text-search-service' i…
markwallace-microsoft May 9, 2024
5348b2f
Updates after merging latest from feature branch
markwallace-microsoft May 9, 2024
1d8b7e0
Update the memory store diagram
markwallace-microsoft May 9, 2024
27a86ac
Code review feedback
markwallace-microsoft May 10, 2024
5904eed
Merge remote-tracking branch 'upstream/main' into users/markwallace/t…
markwallace-microsoft Jun 10, 2024
c0341e6
Add property for Filter and move new concept tests
markwallace-microsoft Jun 10, 2024
acd0d18
Fix warnings
markwallace-microsoft Jun 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
332 changes: 332 additions & 0 deletions docs/decisions/00NN-text-search-service.md

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions docs/decisions/diagrams/text-search-service-abstraction.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: ITextSearchService
---
classDiagram
%% Use https://mermaid.live/ to preview this diagram. The VS Code extension does not handle namespaces.
direction TB

namespace Connectors_Memory_AzureAISearch {
class AzureAISearchService~T~ {
SearchAsync~T~(query, searchSettings, cancellationToken) Task~KernelSearchResults~T~~
}

class AzureAISearchExecutionSettings {
}
}

namespace Plugins_Web {
class BingTextSearchService~T~ {
SearchAsync~T~(query, searchSettings, cancellationToken) Task~KernelSearchResults~T~~
}

class GoogleTextSearchService~T~ {
SearchAsync~T~(query, searchSettings, cancellationToken) Task~KernelSearchResults~T~~
}
}

namespace Search {
class KernelSearchResults~T~ {
+long? TotalCount
+object? InnerContent
+IReadOnlyDictionary? Metadata
+IAsyncEnumerable~T~ Results
}

class ITextSearchService~T~ {
<<interface>>
SearchAsync~T~(query, searchSettings, cancellationToken) Task~KernelSearchResults~T~~
}

class SearchExecutionSettings {
+string Index
+int Count
+int Offset
+IDictionary ExtensionData
}

class TextSearchResult {
+string? Name
+string? Value
+string? Link
}
}

ITextSearchService ..> SearchExecutionSettings
ITextSearchService ..> KernelSearchResults
BingTextSearchService --|> ITextSearchService
GoogleTextSearchService --|> ITextSearchService
AzureAISearchService --|> ITextSearchService
AzureAISearchExecutionSettings --|> SearchExecutionSettings

BingTextSearchService ..> TextSearchResult
GoogleTextSearchService ..> TextSearchResult
AzureAISearchService ..> TextSearchResult

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions docs/decisions/diagrams/text-search-service-imemorystore.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
block-beta
columns 1
block:Plugin
PluginAbstraction[["TextMemoryPlugin"]]
PluginDescription["Provides a plugin to save or recall information from the long or short term memory."]
end
space
block:SemanticMemory
SemanticMemoryAbstraction[["ISemanticTextMemory"]]
SemanticMemoryDescription["An interface for semantic memory that creates and recalls memories associated with text."]
end
space
block:Memory
MemoryAbstraction[["IMemoryStore"]]
MemoryDescription["An interface for storing and retrieving indexed MemoryRecord objects in a data store."]
end
space
block:MemoryClient
MemoryClientAbstraction[["Memory Client"]]
MemoryClientDescription["A database specific service client."]
end
space
block:VectorDatabaseService
VectorDatabase[["Vector Database"]]
VectorDatabaseDescription["A vector database e.g. Redis, Milvus, Pinecone, Qdrant."]
end
Plugin-- "Uses the provided Semantic Memory implementation to recall text memories." -->SemanticMemory
SemanticMemory-- "Uses the provided Memory Store to query the database." -->Memory
Memory-- "Uses a Memory Client (if available) to interact with the database." -->MemoryClient
MemoryClient-- "Invokes the Vector Database Service." -->VectorDatabaseService
style PluginDescription fill:#FFF,stroke-width:0px
style SemanticMemoryDescription fill:#FFF,stroke-width:0px
style MemoryDescription fill:#FFF,stroke-width:0px
style MemoryClientDescription fill:#FFF,stroke-width:0px
style VectorDatabaseDescription fill:#FFF,stroke-width:0px
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
block-beta
columns 1
block:Plugin
PluginAbstraction[["WebSearchEnginePlugin"]]
PluginDescription["Provides a plugin to perform a web search."]
end
space
block:Connector
ConnectorAbstraction[["IWebSearchEngineConnector"]]
ConnectorDescription["An interface for a web search engine connector.<br/>Returns either search result summaries or a normalised <code>WebPage</code> instance."]
end
space
block:Service
ServiceType[["Web Search Service"]]
ServiceDescription["A web search service e.g. Bing, Google, ..."]
end

Plugin-- "Uses the provided IWebSearchEngineConnector implementation to invoke a web search engine." -->Connector
Connector-- "Invokes the web search engine using REST or a service client." -->Service

style PluginDescription fill:#FFF,stroke-width:0px
style ConnectorDescription fill:#FFF,stroke-width:0px
style ServiceDescription fill:#FFF,stroke-width:0px
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ namespace ChatCompletion;
/// potentially reshape our understanding of climate change.
/// </para>
/// 2. Set your secrets:
/// <para> dotnet user-secrets set "AzureAISearch:Endpoint" "https://... .search.windows.net"</para>
/// <para> dotnet user-secrets set "AzureAISearch:ApiKey" "{Key from your Search service resource}"</para>
/// <para> dotnet user-secrets set "AzureAISearch:IndexName" "..."</para>
/// <para> dotnet user-secrets set "AzureAISearchExample:Endpoint" "https://... .search.windows.net"</para>
/// <para> dotnet user-secrets set "AzureAISearchExample:ApiKey" "{Key from your Search service resource}"</para>
/// <para> dotnet user-secrets set "AzureAISearchExample:IndexName" "..."</para>
/// </value>
public class AzureOpenAIWithData_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
{
Expand Down
85 changes: 85 additions & 0 deletions dotnet/samples/Concepts/Search/AzureAISearchExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json.Serialization;
using Azure.Search.Documents.Models;
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
using Microsoft.SemanticKernel.Search;

namespace Search;

/// <summary>
/// This example shows how to create and use a <see cref="AzureAITextSearchService"/>.
/// </summary>
public sealed class AzureAISearchExample(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Show how to create a <see cref="AzureAITextSearchService"/> and use it to perform a text search.
/// </summary>
[Fact]
public async Task SearchAsync()
{
var query = "What is the Semantic Kernel?";
var IndexName = TestConfiguration.AzureAISearch.IndexName;

// Create a search service with Azure AI search
var searchService = new AzureAITextSearchService(
endpoint: TestConfiguration.AzureAISearch.Endpoint,
adminKey: TestConfiguration.AzureAISearch.ApiKey);

// Search with a custom search result type
KernelSearchResults<CustomSearchResult> searchResults = await searchService.SearchAsync<CustomSearchResult>(query, new() { Index = IndexName, Count = 2, Offset = 2 });
await foreach (CustomSearchResult result in searchResults.Results)
{
Console.WriteLine($"Title: {result.Title}");
Console.WriteLine($"Chunk Id: {result.ChunkId}");
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
Console.WriteLine(result.Chunk);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}

// Search for just the summaries
AzureAISearchExecutionSettings settings = new() { Index = IndexName, Count = 2, Offset = 2, ValueField = "chunk" };
KernelSearchResults<string> summaryResults = await searchService.SearchAsync<string>(query, settings);
await foreach (string result in summaryResults.Results)
{
Console.WriteLine(result);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}

// Search with TextSearchResult result type
settings = new() { Index = IndexName, Count = 2, Offset = 2, NameField = "title", ValueField = "chunk", LinkField = "metadata_spo_item_weburi" };
KernelSearchResults<TextSearchResult> textResults = await searchService.SearchAsync<TextSearchResult>(query, settings);
await foreach (TextSearchResult result in textResults.Results)
{
Console.WriteLine($"Name: {result.Name}");
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
Console.WriteLine(result.Value);
Console.WriteLine(result.Link);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}

// Search with a the default result type
KernelSearchResults<SearchDocument> fullResults = await searchService.SearchAsync<SearchDocument>(query, new() { Index = IndexName, Count = 2, Offset = 6 });
await foreach (SearchDocument result in fullResults.Results)
{
Console.WriteLine($"Title: {result.GetString("title")}");
Console.WriteLine($"Chunk Id: {result.GetString("chunk_id")}");
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
Console.WriteLine(result.GetString("chunk"));
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}
}

/// <summary>
/// Represents a custom search result.
/// </summary>
public class CustomSearchResult
{
[JsonPropertyName("title")]
public string? Title { get; set; }
[JsonPropertyName("chunk_id")]
public string? ChunkId { get; set; }
[JsonPropertyName("chunk")]
public string? Chunk { get; set; }
}
}
82 changes: 82 additions & 0 deletions dotnet/samples/Concepts/Search/BingSearchExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
using Microsoft.SemanticKernel.Plugins.Web.Bing;
using Microsoft.SemanticKernel.Search;

namespace Search;

/// <summary>
/// This example shows how to create and use a <see cref="BingTextSearchService"/>.
/// </summary>
public sealed class BingSearchExample(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Show how to create a <see cref="BingTextSearchService"/> and use it to perform a text search.
/// </summary>
[Fact]
public async Task SearchAsync()
{
var query = "What is the Semantic Kernel?";

// Create a search service with Bing search service
var searchService = new BingTextSearchService(
endpoint: TestConfiguration.Bing.Endpoint,
apiKey: TestConfiguration.Bing.ApiKey);

// Search with a custom search result type
KernelSearchResults<CustomSearchResult> searchResults = await searchService.SearchAsync<CustomSearchResult>(query, new() { Count = 2 });
await foreach (CustomSearchResult result in searchResults.Results)
{
Console.WriteLine($"Title: {result.Name}");
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
Console.WriteLine(result.Snippet);
Console.WriteLine(result.Url);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}

// Search for just the summaries
KernelSearchResults<string> summaryResults = await searchService.SearchAsync<string>(query, new() { Count = 2, Offset = 2 });
await foreach (string result in summaryResults.Results)
{
Console.WriteLine(result);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}

// Search with TextSearchResult result type
KernelSearchResults<TextSearchResult> textResults = await searchService.SearchAsync<TextSearchResult>(query, new() { Count = 2, Offset = 4 });
await foreach (TextSearchResult result in textResults.Results)
{
Console.WriteLine($"Name: {result.Name}");
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
Console.WriteLine(result.Value);
Console.WriteLine(result.Link);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}

// Search with a the default result type
KernelSearchResults<BingWebPage> fullResults = await searchService.SearchAsync<BingWebPage>(query, new() { Count = 2, Offset = 6 });
await foreach (BingWebPage result in fullResults.Results)
{
Console.WriteLine($"Name: {result.Name}");
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
Console.WriteLine(result.Snippet);
Console.WriteLine(result.Url);
Console.WriteLine(result.DisplayUrl);
Console.WriteLine(result.DateLastCrawled);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}
}

/// <summary>
/// Represents a custom search result.
/// </summary>
public class CustomSearchResult
{
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("url")]
public Uri? Url { get; set; }
[JsonPropertyName("snippet")]
public string? Snippet { get; set; }
}
}
58 changes: 58 additions & 0 deletions dotnet/samples/Concepts/Search/GoogleSearchExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft. All rights reserved.
using Google.Apis.CustomSearchAPI.v1.Data;
using Microsoft.SemanticKernel.Plugins.Web.Google;
using Microsoft.SemanticKernel.Search;

namespace Search;

/// <summary>
/// This example shows how to create and use a <see cref="GoogleTextSearchService"/>.
/// </summary>
public sealed class GoogleSearchExample(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Show how to create a <see cref="GoogleTextSearchService"/> and use it to perform a text search.
/// </summary>
[Fact]
public async Task SearchAsync()
{
var query = "What is the Semantic Kernel?";

// Create a search service with Azure AI search
var searchService = new GoogleTextSearchService(
searchEngineId: TestConfiguration.Google.SearchEngineId,
apiKey: TestConfiguration.Google.ApiKey);

// Search for just the summaries
KernelSearchResults<string> summaryResults = await searchService.SearchAsync<string>(query, new() { Count = 2, Offset = 2 });
await foreach (string result in summaryResults.Results)
{
Console.WriteLine(result);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}

// Search with TextSearchResult result type
KernelSearchResults<TextSearchResult> textResults = await searchService.SearchAsync<TextSearchResult>(query, new() { Count = 2, Offset = 4 });
await foreach (TextSearchResult result in textResults.Results)
{
Console.WriteLine($"Name: {result.Name}");
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
Console.WriteLine(result.Value);
Console.WriteLine(result.Link);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}

// Search with a the default result type
KernelSearchResults<Result> fullResults = await searchService.SearchAsync<Result>(query, new() { Count = 2, Offset = 6 });
await foreach (Result result in fullResults.Results)
{
Console.WriteLine($"Title: {result.Title}");
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
Console.WriteLine(result.Snippet);
Console.WriteLine(result.Link);
Console.WriteLine(result.DisplayLink);
Console.WriteLine(result.Kind);
Console.WriteLine("------------------------------------------------------------------------------------------------------------------");
}
}
}