Elastic.SemanticKernel.Playground/Program.cs (92 lines of code) (raw):
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
namespace Elastic.SemanticKernel.Playground;
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
internal sealed class Program
{
public static async Task Main(string[] args)
{
#pragma warning disable SKEXP0010 // Some SK methods are still experimental
var builder = Host.CreateApplicationBuilder(args);
// Register AI services.
var kernelBuilder = builder.Services.AddKernel();
kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");
kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");
// Register text search service.
kernelBuilder.AddVectorStoreTextSearch<Hotel>();
// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
.Authentication(new BasicAuthentication("elastic", "my_password"));
kernelBuilder.AddElasticsearchVectorStoreRecordCollection<string, Hotel>("skhotels", elasticsearchClientSettings);
// Build the host.
using var host = builder.Build();
// For demo purposes, we access the services directly without using a DI context.
var kernel = host.Services.GetService<Kernel>()!;
var embeddings = host.Services.GetService<ITextEmbeddingGenerationService>()!;
var vectorStoreCollection = host.Services.GetService<IVectorStoreRecordCollection<string, Hotel>>()!;
// Register search plugin.
var textSearch = host.Services.GetService<VectorStoreTextSearch<Hotel>>()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));
// Crate collection and ingest a few demo records.
await vectorStoreCollection.CreateCollectionIfNotExistsAsync();
// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
.Select(x => x.Split(';'));
foreach (var chunk in hotels.Chunk(25))
{
var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x => x[2]).ToArray());
for (var i = 0; i < chunk.Length; ++i)
{
var hotel = chunk[i];
await vectorStoreCollection.UpsertAsync(new Hotel
{
HotelId = hotel[0],
HotelName = hotel[1],
Description = hotel[2],
DescriptionEmbedding = descriptionEmbeddings[i],
ReferenceLink = hotel[3]
});
}
}
// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
promptTemplate: """
Please use this information to answer the question:
{{#with (SearchPlugin-GetTextSearchResults question)}}
{{#each this}}
Name: {{Name}}
Value: {{Value}}
Source: {{Link}}
-----------------
{{/each}}
{{/with}}
Include the source of relevant information in the response.
Question: {{question}}
""",
arguments: new KernelArguments
{
{ "question", "Please show me all hotels that have a rooftop bar." },
},
templateFormat: "handlebars",
promptTemplateFactory: new HandlebarsPromptTemplateFactory());
Console.WriteLine(response.ToString());
// > Urban Chic Hotel has a rooftop bar with stunning views (Source: https://example.com/stu654).
}
}
public sealed record Hotel
{
[VectorStoreRecordKey]
public required string HotelId { get; set; }
[TextSearchResultName]
[VectorStoreRecordData(IsFilterable = true)]
public required string HotelName { get; set; }
[TextSearchResultValue]
[VectorStoreRecordData(IsFullTextSearchable = true)]
public required string Description { get; set; }
[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
public ReadOnlyMemory<float>? DescriptionEmbedding { get; set; }
[TextSearchResultLink]
[VectorStoreRecordData]
public string? ReferenceLink { get; set; }
}