mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 11:19:56 +00:00
"KernelSyntaxExamples" is the only outstanding project under /samples that still uses project references to access the kernel libraries. This change consolidates all the building, linked dotnet SDK code in SK-dotnet.sln into a single top-level folder. This will simplify build jobs, automatic code owner assignment, and other dev ops tasks. Coming next: other projects in samples will get removed from the dotnet/SK-dotnet.sln solution, and a new solution with dedicated builds created for the /samples folder. TBD: - Language-specific code "samples" will move into a similar subfolder for each language. - The current "/samples" folder (at the root) will likely become something like "apps", with the demo apps promoted to the top-level folder for better discoverability. - notebooks may also move to this folder in the future. Co-authored-by: name <email>
258 lines
10 KiB
C#
258 lines
10 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SemanticKernel.AI.Embeddings;
|
|
using Microsoft.SemanticKernel.AI.Embeddings.VectorOperations;
|
|
using Microsoft.SemanticKernel.Memory;
|
|
using Microsoft.SemanticKernel.Memory.Collections;
|
|
|
|
#pragma warning disable CA2201 // System.Exception is not sufficiently specific - this is a sample
|
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
/// <summary>
|
|
/// This sample provides a custom implementation of <see cref="IMemoryStore"/> that is read only.
|
|
/// In this sample, the data is stored in a JSON string and deserialized into an
|
|
/// <see cref="IEnumerable{MemoryRecord}"/>. For this specific sample, the implementation
|
|
/// of <see cref="IMemoryStore"/> has a single collection, and thus does not need to be named.
|
|
/// It also assumes that the JSON formatted data can be deserialized into <see cref="MemoryRecord"/> objects.
|
|
/// </summary>
|
|
public static class Example25_ReadOnlyMemoryStore
|
|
{
|
|
public static async Task RunAsync()
|
|
{
|
|
var store = new ReadOnlyMemoryStore(s_jsonVectorEntries);
|
|
|
|
var embedding = new Embedding<float>(new float[] { 22, 4, 6 });
|
|
|
|
Console.WriteLine("Reading data from custom read-only memory store");
|
|
var memoryRecord = await store.GetAsync("collection", "key3");
|
|
if (memoryRecord != null)
|
|
{
|
|
Console.WriteLine("ID = {0}, Embedding = {1}", memoryRecord.Metadata.Id, string.Join(", ", memoryRecord.Embedding.Vector));
|
|
}
|
|
|
|
Console.WriteLine("Getting most similar vector to {0}", string.Join(", ", embedding.Vector));
|
|
var result = await store.GetNearestMatchAsync("collection", embedding, 0.0);
|
|
if (result.HasValue)
|
|
{
|
|
Console.WriteLine("Embedding = {0}, Similarity = {1}", string.Join(", ", result.Value.Item1.Embedding.Vector), result.Value.Item2);
|
|
}
|
|
}
|
|
|
|
private sealed class ReadOnlyMemoryStore : IMemoryStore
|
|
{
|
|
private readonly MemoryRecord[]? _memoryRecords = null;
|
|
private readonly int _vectorSize = 3;
|
|
|
|
public ReadOnlyMemoryStore(string valueString)
|
|
{
|
|
s_jsonVectorEntries = s_jsonVectorEntries.Replace("\n", string.Empty, StringComparison.Ordinal);
|
|
s_jsonVectorEntries = s_jsonVectorEntries.Replace(" ", string.Empty, StringComparison.Ordinal);
|
|
this._memoryRecords = JsonSerializer.Deserialize<MemoryRecord[]>(valueString);
|
|
|
|
if (this._memoryRecords == null)
|
|
{
|
|
throw new Exception("Unable to deserialize memory records");
|
|
}
|
|
}
|
|
|
|
public Task CreateCollectionAsync(string collectionName, CancellationToken cancellationToken = default)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public Task DeleteCollectionAsync(string collectionName, CancellationToken cancellationToken = default)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public Task<bool> DoesCollectionExistAsync(string collectionName, CancellationToken cancellationToken = default)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public Task<MemoryRecord?> GetAsync(string collectionName, string key, bool withEmbedding = false, CancellationToken cancellationToken = default)
|
|
{
|
|
// Note: with this simple implementation, the MemoryRecord will always contain the embedding.
|
|
return Task.FromResult(this._memoryRecords?.FirstOrDefault(x => x.Key == key));
|
|
}
|
|
|
|
public async IAsyncEnumerable<MemoryRecord> GetBatchAsync(string collectionName, IEnumerable<string> keys, bool withEmbeddings = false, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
|
{
|
|
// Note: with this simple implementation, the MemoryRecord will always contain the embedding.
|
|
if (this._memoryRecords is not null)
|
|
{
|
|
foreach (var memoryRecord in this._memoryRecords)
|
|
{
|
|
if (keys.Contains(memoryRecord.Key))
|
|
{
|
|
yield return memoryRecord;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public IAsyncEnumerable<string> GetCollectionsAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public async Task<(MemoryRecord, double)?> GetNearestMatchAsync(string collectionName, Embedding<float> embedding, double minRelevanceScore = 0,
|
|
bool withEmbedding = false, CancellationToken cancellationToken = default)
|
|
{
|
|
// Note: with this simple implementation, the MemoryRecord will always contain the embedding.
|
|
await foreach (var item in this.GetNearestMatchesAsync(
|
|
collectionName: collectionName,
|
|
embedding: embedding,
|
|
limit: 1,
|
|
minRelevanceScore: minRelevanceScore,
|
|
withEmbeddings: withEmbedding,
|
|
cancellationToken: cancellationToken).ConfigureAwait(false))
|
|
{
|
|
return item;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
public async IAsyncEnumerable<(MemoryRecord, double)> GetNearestMatchesAsync(string collectionName, Embedding<float> embedding, int limit,
|
|
double minRelevanceScore = 0, bool withEmbeddings = false, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
|
{
|
|
// Note: with this simple implementation, the MemoryRecord will always contain the embedding.
|
|
if (this._memoryRecords == null || this._memoryRecords.Length == 0)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
if (embedding.Count != this._vectorSize)
|
|
{
|
|
throw new Exception($"Embedding vector size {embedding.Count} does not match expected size of {this._vectorSize}");
|
|
}
|
|
|
|
TopNCollection<MemoryRecord> embeddings = new(limit);
|
|
|
|
foreach (var item in this._memoryRecords)
|
|
{
|
|
double similarity = embedding.AsReadOnlySpan().CosineSimilarity(item.Embedding.AsReadOnlySpan());
|
|
if (similarity >= minRelevanceScore)
|
|
{
|
|
embeddings.Add(new(item, similarity));
|
|
}
|
|
}
|
|
|
|
embeddings.SortByScore();
|
|
|
|
foreach (var item in embeddings)
|
|
{
|
|
yield return (item.Value, item.Score.Value);
|
|
}
|
|
}
|
|
|
|
public Task RemoveAsync(string collectionName, string key, CancellationToken cancellationToken = default)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public Task RemoveBatchAsync(string collectionName, IEnumerable<string> keys, CancellationToken cancellationToken = default)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public Task<string> UpsertAsync(string collectionName, MemoryRecord record, CancellationToken cancellationToken = default)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
|
|
public IAsyncEnumerable<string> UpsertBatchAsync(string collectionName, IEnumerable<MemoryRecord> records, CancellationToken cancellationToken = default)
|
|
{
|
|
throw new System.NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private static string s_jsonVectorEntries = @"[
|
|
{
|
|
""embedding"": {
|
|
""vector"": [0, 0, 0 ]
|
|
},
|
|
""metadata"": {
|
|
""is_reference"": false,
|
|
""external_source_name"": ""externalSourceName"",
|
|
""id"": ""Id1"",
|
|
""description"": ""description"",
|
|
""text"": ""text"",
|
|
""additional_metadata"" : ""value:""
|
|
},
|
|
""key"": ""key1"",
|
|
""timestamp"": null
|
|
},
|
|
{
|
|
""embedding"": {
|
|
""vector"": [0, 0, 10 ]
|
|
},
|
|
""metadata"": {
|
|
""is_reference"": false,
|
|
""external_source_name"": ""externalSourceName"",
|
|
""id"": ""Id2"",
|
|
""description"": ""description"",
|
|
""text"": ""text"",
|
|
""additional_metadata"" : ""value:""
|
|
},
|
|
""key"": ""key2"",
|
|
""timestamp"": null
|
|
},
|
|
{
|
|
""embedding"": {
|
|
""vector"": [1, 2, 3 ]
|
|
},
|
|
""metadata"": {
|
|
""is_reference"": false,
|
|
""external_source_name"": ""externalSourceName"",
|
|
""id"": ""Id3"",
|
|
""description"": ""description"",
|
|
""text"": ""text"",
|
|
""additional_metadata"" : ""value:""
|
|
},
|
|
""key"": ""key3"",
|
|
""timestamp"": null
|
|
},
|
|
{
|
|
""embedding"": {
|
|
""vector"": [-1, -2, -3 ]
|
|
},
|
|
""metadata"": {
|
|
""is_reference"": false,
|
|
""external_source_name"": ""externalSourceName"",
|
|
""id"": ""Id4"",
|
|
""description"": ""description"",
|
|
""text"": ""text"",
|
|
""additional_metadata"" : ""value:""
|
|
},
|
|
""key"": ""key4"",
|
|
""timestamp"": null
|
|
},
|
|
{
|
|
""embedding"": {
|
|
""vector"": [12, 8, 4 ]
|
|
},
|
|
""metadata"": {
|
|
""is_reference"": false,
|
|
""external_source_name"": ""externalSourceName"",
|
|
""id"": ""Id5"",
|
|
""description"": ""description"",
|
|
""text"": ""text"",
|
|
""additional_metadata"" : ""value:""
|
|
},
|
|
""key"": ""key5"",
|
|
""timestamp"": null
|
|
}
|
|
]";
|
|
}
|