Basically working VolatileMemoryStore (#1198)

### Motivation and Context
<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->
Initial port of C# memory to Java. Includes VolatileMemoryStore, no
others.


### Description
<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->
Introduces
semantickernel-api/src/main/java/com/microsoft/semantickernel/memory and
implementation in
semantickernel-core/src/main/java/com/microsoft/semantickernel/memory

e2e integration test provides a sample. See
java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/TextEmbeddingsTest.java

### Contribution Checklist
<!-- Before submitting this PR, please make sure: -->
- [x] The code builds clean without any errors or warnings
- [x] The PR follows SK Contribution Guidelines
(https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
- [x] -The code follows the .NET coding conventions
(https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions)
verified with `dotnet format`- Java code uses AOSP style
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone 😄
This commit is contained in:
David Grieve
2023-05-24 20:38:23 +01:00
committed by GitHub
parent 0e7a7db8aa
commit 688d4a4a1b
13 changed files with 346 additions and 159 deletions
@@ -9,6 +9,7 @@ import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.KernelConfig;
import com.microsoft.semantickernel.builders.SKBuilders;
import com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion;
import com.microsoft.semantickernel.memory.VolatileMemoryStore;
import com.microsoft.semantickernel.textcompletion.TextCompletion;
import org.junit.jupiter.api.condition.EnabledIf;
@@ -33,7 +34,10 @@ public class AbstractKernelTest {
.addTextCompletionService(model, kernel -> textCompletion)
.build();
return SKBuilders.kernel().setKernelConfig(kernelConfig).build();
return SKBuilders.kernel()
.setKernelConfig(kernelConfig)
.withMemoryStore(new VolatileMemoryStore())
.build();
}
public static OpenAIAsyncClient getOpenAIClient() throws IOException {
@@ -8,7 +8,9 @@ import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import com.microsoft.semantickernel.builders.SKBuilders;
import com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGeneration;
import com.microsoft.semantickernel.coreskills.TextMemorySkill;
import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection;
import com.microsoft.semantickernel.memory.MemoryQueryResult;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.memory.VolatileMemoryStore;
import com.microsoft.semantickernel.textcompletion.CompletionSKContext;
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
@@ -18,8 +20,6 @@ import org.junit.jupiter.api.condition.EnabledIf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -57,13 +57,9 @@ public class TextEmbeddingsTest extends AbstractKernelTest {
@Test
@EnabledIf("isAzureTestEnabled")
public void testMemory() throws IOException {
String model = "text-embedding-ada-002";
EmbeddingGeneration<String, Float> embeddingGeneration =
new OpenAITextEmbeddingGeneration(getAzureOpenAIClient(), model);
Kernel kernel = buildTextEmbeddingsKernel();
ReadOnlyFunctionCollection memory = kernel.importSkill(new TextMemorySkill(), null);
Kernel kernel = buildTextCompletionKernel();
kernel.importSkill(new TextMemorySkill(), "aboutMe");
String skPrompt =
"\n"
@@ -83,11 +79,64 @@ public class TextEmbeddingsTest extends AbstractKernelTest {
+ "User: {{$userInput}}\n"
+ "ChatBot: ";
Mono<CompletionSKContext> mono =
memory.getFunction("retrieve", CompletionSKFunction.class).invokeAsync("");
CompletionSKContext result = mono.block();
CompletionSKFunction chat =
kernel.getSemanticFunctionBuilder()
.createFunction(
skPrompt, "recall", "aboutMe", "TextEmbeddingTest#testMemory");
LOGGER.info(result.getResult());
VolatileMemoryStore volatileMemoryStore = new VolatileMemoryStore();
volatileMemoryStore.createCollectionAsync("aboutMe").block();
OpenAITextEmbeddingGeneration embeddingGeneration =
new OpenAITextEmbeddingGeneration(getAzureOpenAIClient(), "text-embedding-ada-002");
SemanticTextMemory memory =
SKBuilders.semanticTextMemory()
.setEmbeddingGenerator(embeddingGeneration)
.setStorage(volatileMemoryStore)
.build();
CompletionSKContext context =
chat.buildContext(SKBuilders.variables().build(), memory, kernel.getSkills());
context.getSemanticMemory()
.saveInformationAsync("aboutMe", "My name is Andrea", "fact1", null, null)
.block();
context.getSemanticMemory()
.saveInformationAsync(
"aboutMe", "I currently work as a tour guide", "fact2", null, null)
.block();
context.getSemanticMemory()
.saveInformationAsync(
"aboutMe", "I've been living in Seattle since 2005", "fact3", null, null)
.block();
context.getSemanticMemory()
.saveInformationAsync(
"aboutMe",
"I visited France and Italy five times since 2015",
"fact4",
null,
null)
.block();
context.getSemanticMemory()
.saveInformationAsync("aboutMe", "My family is from New York", "fact5", null, null)
.block();
String query = "Where are you from originally?";
List<MemoryQueryResult> results =
memory.searchAsync("aboutMe", query, 10, .5, false).block();
results.forEach(
result ->
System.out.println(
String.format(
"%s %s (relevance=%f)",
query,
result.getMetadata().getText(),
result.getRelevance())));
}
public void testEmbeddingGeneration(OpenAIAsyncClient client, int expectedEmbeddingSize) {
@@ -3,6 +3,7 @@ package com.microsoft.semantickernel;
import com.microsoft.semantickernel.builders.BuildersSingleton;
import com.microsoft.semantickernel.exceptions.SkillsNotFoundException;
import com.microsoft.semantickernel.memory.MemoryStore;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.orchestration.ContextVariables;
import com.microsoft.semantickernel.orchestration.SKContext;
@@ -36,19 +37,19 @@ public interface Kernel {
PromptTemplateEngine getPromptTemplateEngine();
/**
* Set the semantic memory to use.
* Return the memory store used by the kernel.
*
* @return the MemoryStore instance
*/
MemoryStore getMemoryStore();
/**
* Set the SemanticTextMemory to use.
*
* @param memory {@link SemanticTextMemory} instance
*/
void registerMemory(SemanticTextMemory memory);
/*
/// <summary>
/// Set the semantic memory to use
/// </summary>
/// <param name="memory">Semantic memory instance</param>
void RegisterMemory(ISemanticTextMemory memory);
*/
/**
* Run a pipeline composed of synchronous and asynchronous functions.
*
@@ -127,6 +128,7 @@ public interface Kernel {
class Builder {
@Nullable private KernelConfig kernelConfig = null;
@Nullable private PromptTemplateEngine promptTemplateEngine = null;
@Nullable private MemoryStore memoryStore = null;
public Builder setKernelConfig(KernelConfig kernelConfig) {
this.kernelConfig = kernelConfig;
@@ -138,6 +140,11 @@ public interface Kernel {
return this;
}
public Builder withMemoryStore(MemoryStore memoryStore) {
this.memoryStore = memoryStore;
return this;
}
public Kernel build() {
if (kernelConfig == null) {
throw new IllegalStateException("Must provide a kernel configuration");
@@ -145,12 +152,14 @@ public interface Kernel {
return BuildersSingleton.INST
.getKernelBuilder()
.build(kernelConfig, promptTemplateEngine);
.build(kernelConfig, promptTemplateEngine, memoryStore);
}
}
interface InternalBuilder {
Kernel build(
KernelConfig kernelConfig, @Nullable PromptTemplateEngine promptTemplateEngine);
KernelConfig kernelConfig,
@Nullable PromptTemplateEngine promptTemplateEngine,
@Nullable MemoryStore memoryStore);
}
}
@@ -3,6 +3,7 @@ package com.microsoft.semantickernel.builders;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.orchestration.ContextVariables;
import com.microsoft.semantickernel.orchestration.SKContext;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
@@ -40,6 +41,8 @@ public enum BuildersSingleton {
"com.microsoft.semantickernel.orchestration.DefaultSemanticSKContext$Builder";
private static final String FALLBACK_PROMPT_TEMPLATE_ENGINE_BUILDER_CLASS =
"com.microsoft.semantickernel.templateengine.DefaultPromptTemplateEngine$Builder";
private static final String FALLBACK_SEMANTIC_TEXT_MEMORY_CLASS =
"com.microsoft.semantickernel.memory.DefaultSemanticTextMemory$Builder";
private final FunctionBuilders functionBuilders;
private final Kernel.InternalBuilder kernelBuilder;
@@ -51,6 +54,8 @@ public enum BuildersSingleton {
private final SKContext.Builder context;
private final PromptTemplateEngine.Builder promptTemplateEngine;
private final SemanticTextMemory.Builder semanticTextMemoryBuilder;
BuildersSingleton() {
try {
functionBuilders =
@@ -82,6 +87,9 @@ public enum BuildersSingleton {
variables =
ServiceLoadUtil.findServiceLoader(
ContextVariables.Builder.class, FALLBACK_VARIABLE_BUILDER_CLASS);
semanticTextMemoryBuilder =
ServiceLoadUtil.findServiceLoader(
SemanticTextMemory.Builder.class, FALLBACK_SEMANTIC_TEXT_MEMORY_CLASS);
context =
ServiceLoadUtil.findServiceLoader(
@@ -152,4 +160,8 @@ public enum BuildersSingleton {
public SKContext.Builder context() {
return context;
}
public SemanticTextMemory.Builder getSemanticTextMemoryBuilder() {
return semanticTextMemoryBuilder;
}
}
@@ -4,6 +4,7 @@ package com.microsoft.semantickernel.builders;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.KernelConfig;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.orchestration.ContextVariables;
import com.microsoft.semantickernel.orchestration.SKContext;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
@@ -36,6 +37,10 @@ public class SKBuilders {
return new KernelConfig.Builder();
}
public static SemanticTextMemory.Builder semanticTextMemory() {
return BuildersSingleton.INST.getSemanticTextMemoryBuilder();
}
public static ReadOnlySkillCollection.Builder skillCollection() {
return BuildersSingleton.INST.getReadOnlySkillCollection();
}
@@ -13,7 +13,7 @@ import javax.annotation.Nullable;
* will invalidate existing metadata stored in persistent vector DBs.
*/
public class MemoryRecord extends DataEntryBase {
@Nonnull private final Embedding<Float> embedding;
@Nonnull private final Embedding<? extends Number> embedding;
@Nonnull private final MemoryRecordMetadata metadata;
@@ -27,7 +27,7 @@ public class MemoryRecord extends DataEntryBase {
*/
public MemoryRecord(
@Nonnull MemoryRecordMetadata metadata,
@Nonnull Embedding<Float> embedding,
@Nonnull Embedding<? extends Number> embedding,
@Nullable String key,
@Nullable ZonedDateTime timestamp) {
super(key, timestamp);
@@ -40,7 +40,7 @@ public class MemoryRecord extends DataEntryBase {
*
* @return The source content embeddings.
*/
public Embedding<Float> getEmbedding() {
public Embedding<? extends Number> getEmbedding() {
return embedding;
}
@@ -123,9 +123,9 @@ public interface MemoryStore {
* @return A collection of tuples where item1 is a {@link MemoryRecord} and item2 is its
* similarity score as a {@code double}.
*/
Mono<Collection<Tuple2<MemoryRecord, Double>>> getNearestMatchesAsync(
Mono<Collection<Tuple2<MemoryRecord, Number>>> getNearestMatchesAsync(
@Nonnull String collectionName,
@Nonnull Embedding<Float> embedding,
@Nonnull Embedding<? extends Number> embedding,
int limit,
double minRelevanceScore,
boolean withEmbeddings);
@@ -141,9 +141,9 @@ public interface MemoryStore {
* @return A tuple consisting of the {@link MemoryRecord} and item2 is its similarity score as a
* {@code double}. Null if no nearest match found.
*/
Mono<Tuple2<MemoryRecord, Double>> getNearestMatchAsync(
Mono<Tuple2<MemoryRecord, ? extends Number>> getNearestMatchAsync(
@Nonnull String collectionName,
@Nonnull Embedding<Float> embedding,
@Nonnull Embedding<? super Number> embedding,
double minRelevanceScore,
boolean withEmbedding);
}
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.memory;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import reactor.core.publisher.Mono;
import java.util.List;
@@ -100,4 +102,13 @@ public interface SemanticTextMemory {
* @return A group of collection names.
*/
public Mono<List<String>> getCollectionsAsync();
interface Builder {
Builder setStorage(@Nonnull MemoryStore storage);
Builder setEmbeddingGenerator(
@Nonnull EmbeddingGeneration<String, ? extends Number> embeddingGenerator);
SemanticTextMemory build();
}
}
@@ -7,6 +7,7 @@ import com.microsoft.semantickernel.builders.SKBuilders;
import com.microsoft.semantickernel.coreskills.SkillImporter;
import com.microsoft.semantickernel.exceptions.NotSupportedException;
import com.microsoft.semantickernel.exceptions.SkillsNotFoundException;
import com.microsoft.semantickernel.memory.MemoryStore;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.orchestration.*;
import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig;
@@ -35,13 +36,13 @@ public class DefaultKernel implements Kernel {
private final KernelConfig kernelConfig;
private final DefaultSkillCollection defaultSkillCollection;
private final PromptTemplateEngine promptTemplateEngine;
@Nullable private SemanticTextMemory memory; // TODO: make this final
private MemoryStore memoryStore;
@Inject
public DefaultKernel(
KernelConfig kernelConfig,
PromptTemplateEngine promptTemplateEngine,
@Nullable SemanticTextMemory memory) {
MemoryStore memoryStore) {
if (kernelConfig == null) {
throw new IllegalArgumentException();
}
@@ -49,12 +50,7 @@ public class DefaultKernel implements Kernel {
this.kernelConfig = kernelConfig;
this.promptTemplateEngine = promptTemplateEngine;
this.defaultSkillCollection = new DefaultSkillCollection();
if (memory != null) {
this.memory = memory.copy();
} else {
this.memory = null;
}
this.memoryStore = memoryStore;
kernelConfig.getSkills().forEach(this::registerSemanticFunction);
}
@@ -188,9 +184,14 @@ public class DefaultKernel implements Kernel {
return promptTemplateEngine;
}
@Override
public MemoryStore getMemoryStore() {
return memoryStore;
}
@Override
public void registerMemory(@Nonnull SemanticTextMemory memory) {
this.memory = memory != null ? memory.copy() : null;
throw new NotSupportedException("Not implemented");
}
@Override
@@ -208,9 +209,10 @@ public class DefaultKernel implements Kernel {
if (pipeline == null || pipeline.length == 0) {
throw new SKException("No parameters provided to pipeline");
}
// TODO: The SemanticTextMemory can be null, but there should be a way to provide it.
// Not sure registerMemory is the right way.
Mono<SKContext<?>> pipelineBuilder =
Mono.just(pipeline[0].buildContext(variables, memory, getSkills()));
Mono.just(pipeline[0].buildContext(variables, null, getSkills()));
for (SKFunction f : Arrays.asList(pipeline)) {
pipelineBuilder =
@@ -232,7 +234,9 @@ public class DefaultKernel implements Kernel {
@Override
public Kernel build(
KernelConfig kernelConfig, @Nullable PromptTemplateEngine promptTemplateEngine) {
KernelConfig kernelConfig,
@Nullable PromptTemplateEngine promptTemplateEngine,
@Nullable MemoryStore memoryStore) {
if (promptTemplateEngine == null) {
promptTemplateEngine = new DefaultPromptTemplateEngine();
}
@@ -243,7 +247,7 @@ public class DefaultKernel implements Kernel {
"It is required to set a kernelConfig to build a kernel");
}
return new DefaultKernel(kernelConfig, promptTemplateEngine, null);
return new DefaultKernel(kernelConfig, promptTemplateEngine, memoryStore);
}
}
}
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel;
import com.microsoft.semantickernel.memory.MemoryStore;
import com.microsoft.semantickernel.memory.VolatileMemoryStore;
import com.microsoft.semantickernel.templateengine.DefaultPromptTemplateEngine;
import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
@@ -10,7 +12,9 @@ public class DefaultKernelBuilder implements Kernel.InternalBuilder {
@Override
public Kernel build(
KernelConfig kernelConfig, @Nullable PromptTemplateEngine promptTemplateEngine) {
KernelConfig kernelConfig,
@Nullable PromptTemplateEngine promptTemplateEngine,
@Nullable MemoryStore memoryStore) {
if (promptTemplateEngine == null) {
promptTemplateEngine = new DefaultPromptTemplateEngine();
}
@@ -18,7 +22,10 @@ public class DefaultKernelBuilder implements Kernel.InternalBuilder {
if (kernelConfig == null) {
throw new IllegalArgumentException();
}
if (memoryStore == null) {
memoryStore = new VolatileMemoryStore();
}
return new DefaultKernel(kernelConfig, promptTemplateEngine, null);
return new DefaultKernel(kernelConfig, promptTemplateEngine, memoryStore);
}
}
@@ -5,8 +5,13 @@ import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import com.microsoft.semantickernel.exceptions.NotSupportedException;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -16,12 +21,12 @@ import javax.annotation.Nullable;
/// </summary>
public class DefaultSemanticTextMemory implements SemanticTextMemory {
@Nonnull private final EmbeddingGeneration<String, Float> _embeddingGenerator;
@Nonnull private final EmbeddingGeneration<String, ? extends Number> _embeddingGenerator;
@Nonnull private /*final*/ MemoryStore _storage;
public DefaultSemanticTextMemory(
@Nonnull MemoryStore storage,
@Nonnull EmbeddingGeneration<String, Float> embeddingGenerator) {
@Nonnull EmbeddingGeneration<String, ? extends Number> embeddingGenerator) {
this._embeddingGenerator = embeddingGenerator;
// TODO: this assignment raises EI_EXPOSE_REP2 in spotbugs (filtered out for now)
this._storage = storage;
@@ -40,30 +45,28 @@ public class DefaultSemanticTextMemory implements SemanticTextMemory {
@Nonnull String id,
@Nullable String description,
@Nullable String additionalMetadata) {
return Mono.error(new NotSupportedException("Pending implementation"));
// var embedding = await this._embeddingGenerator.GenerateEmbeddingAsync(text,
// cancellationToken: cancel);
// MemoryRecord data = MemoryRecord.LocalRecord(
// id: id, text: text, description: description, additionalMetadata:
// additionalMetadata, embedding: embedding);
//
// if (!(await this._storage.DoesCollectionExistAsync(collection, cancel)))
// {
// await this._storage.CreateCollectionAsync(collection, cancel);
// }
//
// return await this._storage.UpsertAsync(collection, record: data, cancel: cancel);
return _embeddingGenerator
.generateEmbeddingsAsync(Collections.singletonList(text))
.flatMap(
embeddings -> {
if (embeddings.isEmpty()) {
return Mono.empty();
}
MemoryRecordMetadata data =
new MemoryRecordMetadata(
true, id, text, description, "", additionalMetadata);
MemoryRecord memoryRecord =
new MemoryRecord(
data, embeddings.iterator().next(), collection, null);
return _storage.upsertAsync(collection, memoryRecord);
});
}
@Override
public Mono<MemoryQueryResult> getAsync(String collection, String key, boolean withEmbedding) {
return Mono.error(new NotSupportedException("Pending implementation"));
// MemoryRecord? record = await this._storage.GetAsync(collection, key,
// withEmbedding, cancel);
//
// if (record == null) { return null; }
//
// return MemoryQueryResult.FromMemoryRecord(record, 1);
return _storage.getAsync(collection, key, withEmbedding)
.map(record -> new MemoryQueryResult(record.getMetadata(), 1d));
}
@Override
@@ -72,6 +75,28 @@ public class DefaultSemanticTextMemory implements SemanticTextMemory {
// await this._storage.RemoveAsync(collection, key, cancel);
}
private static final Function<
Collection<Tuple2<MemoryRecord, Number>>, Mono<List<MemoryQueryResult>>>
transformMatchesToResults =
records -> {
if (records.isEmpty()) {
return Mono.empty();
}
return Mono.just(
records.stream()
.map(
record -> {
Tuple2<MemoryRecord, ? extends Number> tuple =
record;
MemoryRecord memoryRecord = tuple.getT1();
Number relevanceScore = tuple.getT2();
return new MemoryQueryResult(
memoryRecord.getMetadata(),
relevanceScore.doubleValue());
})
.collect(Collectors.toList()));
};
@Override
public Mono<List<MemoryQueryResult>> searchAsync(
@Nonnull String collection,
@@ -79,24 +104,23 @@ public class DefaultSemanticTextMemory implements SemanticTextMemory {
int limit,
double minRelevanceScore,
boolean withEmbeddings) {
return Mono.error(new NotSupportedException("Pending implementation"));
// Embedding<float> queryEmbedding = await
// this._embeddingGenerator.GenerateEmbeddingAsync(query, cancellationToken: cancel);
//
// IAsyncEnumerable<(MemoryRecord, double)> results =
// this._storage.GetNearestMatchesAsync(
// collectionName: collection,
// embedding: queryEmbedding,
// limit: limit,
// minRelevanceScore: minRelevanceScore,
// withEmbeddings: withEmbeddings,
// cancel: cancel);
//
// await foreach ((MemoryRecord, double) result in results.WithCancellation(cancel))
// {
// yield return MemoryQueryResult.FromMemoryRecord(result.Item1, result.Item2);
// }
// TODO: break this up into smaller methods
return _embeddingGenerator
.generateEmbeddingsAsync(Collections.singletonList(query))
.flatMap(
embeddings -> {
if (embeddings.isEmpty()) {
return Mono.empty();
}
return _storage.getNearestMatchesAsync(
collection,
embeddings.iterator().next(),
limit,
minRelevanceScore,
withEmbeddings)
.flatMap(transformMatchesToResults);
});
}
@Override
@@ -132,4 +156,34 @@ public class DefaultSemanticTextMemory implements SemanticTextMemory {
public SemanticTextMemory merge(MemoryQueryResult b) {
throw new NotSupportedException("Pending implementation");
}
public static class Builder implements SemanticTextMemory.Builder {
@Nullable MemoryStore storage = null;
@Nullable EmbeddingGeneration<String, ? extends Number> embeddingGenerator = null;
@Override
public Builder setStorage(@Nonnull MemoryStore storage) {
this.storage = storage;
return this;
}
@Override
public Builder setEmbeddingGenerator(
@Nonnull EmbeddingGeneration<String, ? extends Number> embeddingGenerator) {
this.embeddingGenerator = embeddingGenerator;
return this;
}
@Override
public SemanticTextMemory build() {
if (storage == null) {
throw new IllegalStateException("Storage must be set");
}
if (embeddingGenerator == null) {
throw new IllegalStateException("Embedding generator must be set");
}
return new DefaultSemanticTextMemory(storage, embeddingGenerator);
}
}
}
@@ -1,18 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.memory;
import com.microsoft.semantickernel.ai.EmbeddingVector;
import com.microsoft.semantickernel.ai.embeddings.Embedding;
import com.microsoft.semantickernel.exceptions.NotSupportedException;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.ToDoubleFunction;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
@@ -28,8 +34,7 @@ public class VolatileMemoryStore implements MemoryStore {
public Mono<Void> createCollectionAsync(@Nonnull String collectionName) {
if (this._store.containsKey(collectionName)) {
throw new MemoryException(
MemoryException.ErrorCodes.FAILED_TO_CREATE_COLLECTION,
String.format("Could not create collection %s", collectionName));
MemoryException.ErrorCodes.FAILED_TO_CREATE_COLLECTION, collectionName);
}
this._store.putIfAbsent(collectionName, new ConcurrentHashMap<>());
return Mono.empty();
@@ -49,8 +54,7 @@ public class VolatileMemoryStore implements MemoryStore {
public Mono<Void> deleteCollectionAsync(@Nonnull String collectionName) {
if (!this._store.containsKey(collectionName)) {
throw new MemoryException(
MemoryException.ErrorCodes.FAILED_TO_DELETE_COLLECTION,
String.format("Could not delete collection %s", collectionName));
MemoryException.ErrorCodes.FAILED_TO_DELETE_COLLECTION, collectionName);
}
this._store.remove(collectionName);
return Mono.empty();
@@ -152,83 +156,83 @@ public class VolatileMemoryStore implements MemoryStore {
return Mono.empty();
}
@SuppressWarnings("unchecked")
private static EmbeddingVector<Number> upcastEmbeddingVector(List<? extends Number> list) {
return new EmbeddingVector<Number>((List<Number>) list);
}
private static final ToDoubleFunction<Tuple2<MemoryRecord, ? extends Number>>
extractSimilarity = tuple -> tuple.getT2().doubleValue();
@Override
public Mono<Collection<Tuple2<MemoryRecord, Double>>> getNearestMatchesAsync(
public Mono<Collection<Tuple2<MemoryRecord, Number>>> getNearestMatchesAsync(
@Nonnull String collectionName,
@Nonnull Embedding<Float> embedding,
@Nonnull Embedding<? extends Number> embedding,
int limit,
double minRelevanceScore,
boolean withEmbeddings) {
return Mono.error(new NotSupportedException("Pending implementation"));
// if (limit <= 0) {
// return Mono.just(Collections.emptyList());
// }
//
// Map<String,MemoryRecord> collection = getCollection(collectionName);
// Collection<MemoryRecord> embeddingCollection = collection.values();
// if (embeddingCollection == null || embeddingCollection.isEmpty()) {
// return Mono.just(Collections.emptyList());
// }
//
// List<Tuple2<MemoryRecord, Double>> nearestMatches = new ArrayList<>();
// embeddingCollection.forEach(record -> {
// if (record != null) {
// double similarity = embedding.cosineSimilarity(record.getEmbedding());
// if (similarity >= minRelevanceScore) {
// if (withEmbeddings) {
// nearestMatches.add(new Tuple2<>(record, similarity));
// } else {
// nearestMatches.add(new
// Tuple2<>(MemoryRecord.fromMetadata(record.getMetadata(), null,
// record.getMetadata().getId(), record.getTimestamp()), similarity));
// }
// }
// }
// });
//
//
// EmbeddingReadOnlySpan<Float> embeddingSpan = new(embedding.AsReadOnlySpan());
//
// TopNCollection<MemoryRecord> embeddings = new(limit);
//
// foreach (var record in embeddingCollection)
// {
// if (record != null)
// {
// double similarity = embedding
// .AsReadOnlySpan()
// .CosineSimilarity(record.Embedding.AsReadOnlySpan());
// if (similarity >= minRelevanceScore)
// {
// var entry = withEmbeddings ? record :
// MemoryRecord.FromMetadata(record.Metadata, Embedding<Float>.Empty, record.Key,
// record.Timestamp);
// embeddings.Add(new(entry, similarity));
// }
// }
// }
//
// embeddings.SortByScore();
//
// return embeddings.Select(x => (x.Value, x.Score.Value)).ToAsyncEnumerable();
if (limit <= 0) {
return Mono.just(Collections.emptyList());
}
Map<String, MemoryRecord> collection = getCollection(collectionName);
if (collection == null || collection.isEmpty()) {
return Mono.just(Collections.emptyList());
}
Collection<MemoryRecord> embeddingCollection = collection.values();
// Upcast the embedding vector to a Number to get around compiler complaining about type
// mismatches.
EmbeddingVector<Number> embeddingVector = upcastEmbeddingVector(embedding.getVector());
Collection<Tuple2<MemoryRecord, Number>> nearestMatches = new ArrayList<>();
embeddingCollection.forEach(
record -> {
if (record != null) {
EmbeddingVector<Number> recordVector =
upcastEmbeddingVector(record.getEmbedding().getVector());
double similarity = embeddingVector.cosineSimilarity(recordVector);
if (similarity >= minRelevanceScore) {
if (withEmbeddings) {
nearestMatches.add(Tuples.of(record, similarity));
} else {
nearestMatches.add(
Tuples.of(
MemoryRecord.fromMetadata(
record.getMetadata(),
null,
record.getMetadata().getId(),
record.getTimestamp()),
similarity));
}
}
}
});
return Mono.just(
nearestMatches.stream()
.sorted(Comparator.comparingDouble(extractSimilarity).reversed())
.limit(limit)
.collect(Collectors.toList()));
}
@Override
public Mono<Tuple2<MemoryRecord, Double>> getNearestMatchAsync(
public Mono<Tuple2<MemoryRecord, ? extends Number>> getNearestMatchAsync(
@Nonnull String collectionName,
@Nonnull Embedding<Float> embedding,
@Nonnull Embedding<? super Number> embedding,
double minRelevanceScore,
boolean withEmbedding) {
return Mono.error(new NotSupportedException("Pending implementation"));
// return await this.GetNearestMatchesAsync(
// collectionName: collectionName,
// embedding: embedding,
// limit: 1,
// minRelevanceScore: minRelevanceScore,
// withEmbeddings: withEmbedding,
// cancellationToken:
// cancellationToken).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
return getNearestMatchesAsync(
collectionName, embedding, 1, minRelevanceScore, withEmbedding)
.flatMap(
nearestMatches -> {
if (nearestMatches.isEmpty()) {
return Mono.empty();
}
return Mono.just(nearestMatches.iterator().next());
});
}
protected Map<String, MemoryRecord> getCollection(@Nonnull String collectionName) {
@@ -236,9 +240,7 @@ public class VolatileMemoryStore implements MemoryStore {
if (collection == null) {
throw new MemoryException(
MemoryException.ErrorCodes.ATTEMPTED_TO_ACCESS_NONEXISTENT_COLLECTION,
String.format(
"Attempted to access a memory collection that does not exist: %s",
collectionName));
collectionName);
}
return collection;
}
+31 -1
View File
@@ -15,11 +15,41 @@
<Field name="_storage"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.orchestration.planner.DefaultSequentialPlannerSKFunction"/>
<Field name="delegate"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.Kernel$Builder"/>
<Method name="withMemoryStore"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.DefaultKernel"/>
<Field name="memoryStore"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.DefaultKernel"/>
<Method name="getMemoryStore"/>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.memory.DefaultSemanticTextMemory$Builder"/>
<Method name="setStorage"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.skilldefinition.DefaultSkillCollection"/>
<Method name="getSkillCollection"/>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
</FindBugsFilter>