forked from microsoft/semantic-kernel
-
Notifications
You must be signed in to change notification settings - Fork 1
/
TextMemoryPlugin_XpoMemoryStore.cs
232 lines (183 loc) · 10.5 KB
/
TextMemoryPlugin_XpoMemoryStore.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
using Microsoft.SemanticKernel.Connectors.Chroma;
using Microsoft.SemanticKernel.Connectors.DuckDB;
using Microsoft.SemanticKernel.Connectors.Kusto;
using Microsoft.SemanticKernel.Connectors.MongoDB;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Connectors.Pinecone;
using Microsoft.SemanticKernel.Connectors.Postgres;
using Microsoft.SemanticKernel.Connectors.Qdrant;
using Microsoft.SemanticKernel.Connectors.Redis;
using Microsoft.SemanticKernel.Connectors.Sqlite;
using Microsoft.SemanticKernel.Connectors.Weaviate;
using Microsoft.SemanticKernel.Connectors.Xpo;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Plugins.Memory;
using Npgsql;
using StackExchange.Redis;
namespace Memory;
public class TextMemoryPlugin_XpoMemoryStore(ITestOutputHelper output) : BaseTest(output)
{
private const string MemoryCollectionName = "aboutMe";
[Fact]
public async Task RunAsync()
{
// Xpo Memory Store - using InMemoryDataStore, an in-memory store that is not persisted
string cnx = DevExpress.Xpo.DB.InMemoryDataStore.GetConnectionStringInMemory(true);
cnx = "Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=XpoKernelMemory";
XpoMemoryStore store = await XpoMemoryStore.ConnectAsync(cnx);
await RunWithStoreAsync(store);
}
private async Task RunWithStoreAsync(IMemoryStore memoryStore)
{
var EmbeddingModelId = "text-embedding-3-small";
var ChatModelId = "gpt-4o";
#pragma warning disable IDE0039
var GetKey = () => Environment.GetEnvironmentVariable("OpenAiTestKey", EnvironmentVariableTarget.Machine);
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(ChatModelId, GetKey.Invoke())
.AddOpenAITextEmbeddingGeneration(EmbeddingModelId, GetKey.Invoke())
.Build();
// Create an embedding generator to use for semantic memory.
var embeddingGenerator = new OpenAITextEmbeddingGenerationService(EmbeddingModelId, GetKey.Invoke());
// The combination of the text embedding generator and the memory store makes up the 'SemanticTextMemory' object used to
// store and retrieve memories.
SemanticTextMemory textMemory = new(memoryStore, embeddingGenerator);
/////////////////////////////////////////////////////////////////////////////////////////////////////
// PART 1: Store and retrieve memories using the ISemanticTextMemory (textMemory) object.
//
// This is a simple way to store memories from a code perspective, without using the Kernel.
/////////////////////////////////////////////////////////////////////////////////////////////////////
Debug.WriteLine("== PART 1a: Saving Memories through the ISemanticTextMemory object ==");
Debug.WriteLine("Saving memory with key 'info1': \"My name is Andrea\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");
Debug.WriteLine("Saving memory with key 'info2': \"I work as a tourist operator\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I work as a tourist operator");
Debug.WriteLine("Saving memory with key 'info3': \"I've been living in Seattle since 2005\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I've been living in Seattle since 2005");
Debug.WriteLine("Saving memory with key 'info4': \"I visited France and Italy five times since 2015\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
// Retrieve a memory
Debug.WriteLine("== PART 1b: Retrieving Memories through the ISemanticTextMemory object ==");
MemoryQueryResult? lookup = await textMemory.GetAsync(MemoryCollectionName, "info1");
Debug.WriteLine("Memory with key 'info1':" + lookup?.Metadata.Text ?? "ERROR: memory not found");
Debug.WriteLine("");
/////////////////////////////////////////////////////////////////////////////////////////////////////
// PART 2: Create TextMemoryPlugin, store and retrieve memories through the Kernel.
//
// This enables prompt functions and the AI (via Planners) to access memories
/////////////////////////////////////////////////////////////////////////////////////////////////////
Debug.WriteLine("== PART 2a: Saving Memories through the Kernel with TextMemoryPlugin and the 'Save' function ==");
// Import the TextMemoryPlugin into the Kernel for other functions
var memoryPlugin = kernel.ImportPluginFromObject(new TextMemoryPlugin(textMemory));
// Save a memory with the Kernel
Debug.WriteLine("Saving memory with key 'info5': \"My family is from New York\"");
await kernel.InvokeAsync(memoryPlugin["Save"], new()
{
[TextMemoryPlugin.InputParam] = "My family is from New York",
[TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
[TextMemoryPlugin.KeyParam] = "info5",
});
// Retrieve a specific memory with the Kernel
Debug.WriteLine("== PART 2b: Retrieving Memories through the Kernel with TextMemoryPlugin and the 'Retrieve' function ==");
var result = await kernel.InvokeAsync(memoryPlugin["Retrieve"], new KernelArguments()
{
[TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
[TextMemoryPlugin.KeyParam] = "info5"
});
Debug.WriteLine("Memory with key 'info5':" + result.GetValue<string>() ?? "ERROR: memory not found");
Debug.WriteLine("");
/////////////////////////////////////////////////////////////////////////////////////////////////////
// PART 3: Recall similar ideas with semantic search
//
// Uses AI Embeddings for fuzzy lookup of memories based on intent, rather than a specific key.
/////////////////////////////////////////////////////////////////////////////////////////////////////
Debug.WriteLine("== PART 3: Recall (similarity search) with AI Embeddings ==");
Debug.WriteLine("== PART 3a: Recall (similarity search) with ISemanticTextMemory ==");
Debug.WriteLine("Ask: where did I grow up?");
await foreach (var answer in textMemory.SearchAsync(
collection: MemoryCollectionName,
query: "where did I grow up?",
limit: 2,
minRelevanceScore: 0.79,
withEmbeddings: true))
{
Debug.WriteLine($"Answer: {answer.Metadata.Text}");
}
Debug.WriteLine("== PART 3b: Recall (similarity search) with Kernel and TextMemoryPlugin 'Recall' function ==");
Debug.WriteLine("Ask: where do I live?");
result = await kernel.InvokeAsync(memoryPlugin["Recall"], new()
{
[TextMemoryPlugin.InputParam] = "Ask: where do I live?",
[TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
[TextMemoryPlugin.LimitParam] = "2",
[TextMemoryPlugin.RelevanceParam] = "0.79",
});
Debug.WriteLine($"Answer: {result.GetValue<string>()}");
Debug.WriteLine("");
/*
Output:
Ask: where did I grow up?
Answer:
["My family is from New York","I\u0027ve been living in Seattle since 2005"]
Ask: where do I live?
Answer:
["I\u0027ve been living in Seattle since 2005","My family is from New York"]
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////
// PART 4: TextMemoryPlugin Recall in a Prompt Function
//
// Looks up related memories when rendering a prompt template, then sends the rendered prompt to
// the text generation model to answer a natural language query.
/////////////////////////////////////////////////////////////////////////////////////////////////////
Debug.WriteLine("== PART 4: Using TextMemoryPlugin 'Recall' function in a Prompt Function ==");
// Build a prompt function that uses memory to find facts
const string RecallFunctionDefinition = @"
Consider only the facts below when answering questions:
BEGIN FACTS
About me: {{recall 'where did I grow up?'}}
About me: {{recall 'where do I live now?'}}
END FACTS
Question: {{$input}}
Answer:
";
var aboutMeOracle = kernel.CreateFunctionFromPrompt(RecallFunctionDefinition, new OpenAIPromptExecutionSettings() { MaxTokens = 100 });
result = await kernel.InvokeAsync(aboutMeOracle, new()
{
[TextMemoryPlugin.InputParam] = "Do I live in the same town where I grew up?",
[TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
[TextMemoryPlugin.LimitParam] = "2",
[TextMemoryPlugin.RelevanceParam] = "0.79",
});
Debug.WriteLine("Ask: Do I live in the same town where I grew up?");
Debug.WriteLine($"Answer: {result.GetValue<string>()}");
/*
Approximate Output:
Answer: No, I do not live in the same town where I grew up since my family is from New York and I have been living in Seattle since 2005.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////
// PART 5: Cleanup, deleting database collection
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
Debug.WriteLine("== PART 5: Cleanup, deleting database collection ==");
Debug.WriteLine("Printing Collections in DB...");
var collections = memoryStore.GetCollectionsAsync();
await foreach (var collection in collections)
{
Debug.WriteLine(collection);
}
Debug.WriteLine("");
Debug.WriteLine($"Removing Collection {MemoryCollectionName}");
await memoryStore.DeleteCollectionAsync(MemoryCollectionName);
Debug.WriteLine("");
Debug.WriteLine($"Printing Collections in DB (after removing {MemoryCollectionName})...");
collections = memoryStore.GetCollectionsAsync();
await foreach (var collection in collections)
{
Debug.WriteLine(collection);
}
}
}