Java: Example14_SemanticMemory (#1355)

### 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.
-->
Port Example14_SemanticMemory.cs to Java

### Description
<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->
Port of Example14_SemanticMemory.cs to Java. 
This sample uses an Azure Cognitive Search connector, which was also
ported from dotnet/src/Connectors/Connectors.Memory.AzureCognitiveSearch
Changes needed to api and core modules. 

### 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 follows AOSP style
- [X] All unit tests pass, and I have added new tests where possible
- [X] I didn't break anyone 😄

---------

Co-authored-by: Luigi Montoya <[email protected]>
Co-authored-by: joe-braley <[email protected]>
Co-authored-by: Luigi96 <[email protected]>
Co-authored-by: Mark Wallace <[email protected]>
Co-authored-by: John Oliver <[email protected]>
This commit is contained in:
David Grieve
2023-06-27 13:02:53 +01:00
committed by GitHub
co-authored by Luigi Montoya joe-braley Luigi96 Mark Wallace John Oliver
parent db0ee80dfe
commit b7f7170a64
23 changed files with 922 additions and 188 deletions
@@ -1,13 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.e2e;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import org.junit.jupiter.api.condition.EnabledIf;
import com.azure.ai.openai.OpenAIAsyncClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.ai.openai.models.NonAzureOpenAIKeyCredential;
@@ -18,6 +11,12 @@ 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;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
@EnabledIf("isAzureTestEnabled")
public class AbstractKernelTest {
@@ -27,10 +26,14 @@ public class AbstractKernelTest {
public static Kernel buildTextCompletionKernel() throws IOException {
String model = "text-davinci-003";
TextCompletion textCompletion = new OpenAITextCompletion(getAzureOpenAIClient(), model);
final OpenAIAsyncClient openAIClient = getAzureOpenAIClient();
TextCompletion textCompletion = new OpenAITextCompletion(openAIClient, model);
KernelConfig kernelConfig = SKBuilders.kernelConfig()
.addTextCompletionService(model, kernel -> textCompletion)
.addTextEmbeddingsGenerationService(
model,
kernel -> SKBuilders.textEmbeddingGenerationService().build(openAIClient, model))
.build();
return SKBuilders.kernel()
@@ -53,9 +53,9 @@ public class TextEmbeddingsTest extends AbstractKernelTest {
LOGGER.info(String.valueOf(embeddingGeneration.generateEmbeddingsAsync(data).block()));
}
@Test
@EnabledIf("isAzureTestEnabled")
public void testMemory() throws IOException {
@Test
@EnabledIf("isAzureTestEnabled")
public void testMemory() throws IOException {
Kernel kernel = buildTextCompletionKernel();
kernel.importSkill(new TextMemorySkill(), "aboutMe");
@@ -96,8 +96,8 @@ public class TextEmbeddingsTest extends AbstractKernelTest {
kernel.getSkills());
context.getSemanticMemory()
.saveInformationAsync("aboutMe", "My name is Andrea", "fact1", null, null)
.block();
.saveInformationAsync("aboutMe", "My name is Andrea", "fact1", null, null)
.block();
context.getSemanticMemory()
.saveInformationAsync(
+2 -105
View File
@@ -19,8 +19,9 @@
<log4j2.version>2.20.0</log4j2.version>
<maven.compiler-plugin.version>3.11.0</maven.compiler-plugin.version>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
@@ -34,108 +35,4 @@
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler-plugin.version}</version>
<configuration>
<source>${maven.compiler.release}</source>
<target>${maven.compiler.release}</target>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.microsoft.semantickernel</groupId>
<artifactId>semantickernel-api</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.semantickernel.connectors</groupId>
<artifactId>semantickernel-connectors</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.semantickernel</groupId>
<artifactId>semantickernel-core</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.microsoft.semantickernel</groupId>
<artifactId>semantickernel-core-skills</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>com.microsoft.semantickernel.extensions</groupId>
<artifactId>sequentialplanner-extensions</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>bug-check</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
<configuration>
<source>${maven.compiler.release}</source>
<target>${maven.compiler.release}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<showWarnings>true</showWarnings>
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<!-- Remove exclusions when more project is stable -->
<arg>-Xplugin:ErrorProne
-XepOpt:NullAway:AnnotatedPackages=com.microsoft.semantickernel
-Xep:AlmostJavadoc:OFF -Xep:MissingSummary:OFF
-Xep:UnusedVariable:OFF -Xep:EmptyBlockTag:OFF
</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>${google.errorprone.core.version}</version>
</path>
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
<version>${com.uber.nullaway.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
+14 -1
View File
@@ -26,7 +26,8 @@
<dependency>
<groupId>com.microsoft.semantickernel</groupId>
<artifactId>semantickernel-core</artifactId>
<scope>runtime</scope>
<!-- TODO: scope should be runtime, but VolatileMemoryStore is an issue -->
<!-- <scope>runtime</scope> -->
</dependency>
<dependency>
<groupId>com.microsoft.semantickernel</groupId>
@@ -54,5 +55,17 @@
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel;
import java.io.IOException;
import com.azure.ai.openai.OpenAIAsyncClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.ai.openai.models.NonAzureOpenAIKeyCredential;
@@ -11,16 +9,18 @@ import com.microsoft.semantickernel.util.AIProviderSettings;
import com.microsoft.semantickernel.util.AzureOpenAISettings;
import com.microsoft.semantickernel.util.OpenAISettings;
import java.io.IOException;
public class Config {
public static final String CONF_PROPERTIES = "java/samples/conf.properties";
public static final String CONF_PROPERTIES =
System.getProperty("CONF_PROPERTIES", "java/samples/conf.properties");
public enum ClientType {
OPEN_AI {
@Override
public OpenAIAsyncClient getClient() throws IOException {
OpenAISettings settings = AIProviderSettings.getOpenAISettingsFromFile(CONF_PROPERTIES);
return new OpenAIClientBuilder()
.credential(new NonAzureOpenAIKeyCredential(settings.getKey()))
.buildAsyncClient();
@@ -32,16 +32,13 @@ public class Config {
throws IOException {
AzureOpenAISettings settings = AIProviderSettings.getAzureOpenAISettingsFromFile(CONF_PROPERTIES);
return new OpenAIClientBuilder().endpoint(settings.getEndpoint())
.credential(new AzureKeyCredential(settings.getKey())).buildAsyncClient();
return new OpenAIClientBuilder()
.endpoint(settings.getEndpoint())
.credential(new AzureKeyCredential(settings.getKey()))
.buildAsyncClient();
}
};
/**
* Returns the client that will handle AzureOpenAI or OpenAI requests.
*
* @return client to be used by the kernel.
*/
public abstract OpenAIAsyncClient getClient() throws IOException;
}
}
@@ -0,0 +1,174 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.syntaxexamples;
import com.microsoft.semantickernel.Config;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.builders.SKBuilders;
import com.microsoft.semantickernel.connectors.memory.azurecognitivesearch.AzureCognitiveSearchMemory;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
/* The files contain two examples about SK Semantic Memory.
*
* 1. Memory using Azure Cognitive Search.
* 2. Memory using a custom embedding generator and vector engine.
*
* Semantic Memory allows to store your data like traditional DBs,
* adding the ability to query it using natural language.
*/
// ReSharper disable once InconsistentNaming
public class Example14_SemanticMemory
{
private static final String MEMORY_COLLECTION_NAME = "SKGitHub";
public static void main(String[] args) throws Exception
{
System.out.println("==============================================================");
System.out.println("======== Semantic Memory using Azure Cognitive Search ========");
System.out.println("==============================================================");
/* This example leverages Azure Cognitive Search to provide SK with Semantic Memory.
*
* Azure Cognitive Search automatically indexes your data semantically, so you don't
* need to worry about embedding generation.
*/
var kernelConfig = SKBuilders.kernelConfig().build();
var kernelWithACS = SKBuilders.kernel()
.setKernelConfig(kernelConfig)
.withMemory(new AzureCognitiveSearchMemory(System.getenv("ACS_ENDPOINT"), System.getenv("ACS_API_KEY")))
.build();
runExampleAsync(kernelWithACS).block();
System.out.println("====================================================");
System.out.println("======== Semantic Memory (volatile, in RAM) ========");
System.out.println("====================================================");
/* You can build your own semantic memory combining an Embedding Generator
* with a Memory storage that supports search by similarity (ie semantic search).
*
* In this example we use a volatile memory, a local simulation of a vector DB.
*
* You can replace VolatileMemoryStore with Qdrant (see QdrantMemoryStore connector)
* or implement your connectors for Pinecone, Vespa, Postgres + pgvector, SQLite VSS, etc.
*/
var openAIAsyncClient = Config.ClientType.AZURE_OPEN_AI.getClient();
var kernelConfigWithTextEmbedding = SKBuilders.kernelConfig()
.addTextEmbeddingsGenerationService(
"ada",
kernel -> SKBuilders.textEmbeddingGenerationService().build(openAIAsyncClient, "text-embedding-ada-002"))
.build();
var kernelWithCustomDb = SKBuilders.kernel()
.setKernelConfig(kernelConfigWithTextEmbedding)
.withMemoryStore(SKBuilders.memoryStore().build())
.build();
runExampleAsync(kernelWithCustomDb).block();
}
public static Mono<Void> runExampleAsync(Kernel kernel)
{
return storeMemoryAsync(kernel)
.then(searchMemoryAsync(kernel, "How do I get started?"))
/*
Output:
Query: How do I get started?
Result 1:
URL: : https://github.com/microsoft/semantic-kernel/blob/main/README.md
Title : README: Installation, getting started, and how to contribute
Result 2:
URL: : https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet-jupyter-notebooks/00-getting-started.ipynb
Title : Jupyter notebook describing how to get started with the Semantic Kernel
*/
.then(searchMemoryAsync(kernel, "Can I build a chat with SK?"));
/*
Output:
Query: Can I build a chat with SK?
Result 1:
URL: : https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT
Title : Sample demonstrating how to create a chat skill interfacing with ChatGPT
Result 2:
URL: : https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/chat-summary-webapp-react/README.md
Title : README: README associated with a sample chat summary react-based webapp
*/
}
private static Mono<Void> searchMemoryAsync(Kernel kernel, String query)
{
return kernel.getMemory().searchAsync(MEMORY_COLLECTION_NAME, query, 2, 0.5, false)
.mapNotNull(memories -> {
System.out.println("\nQuery: " + query + "\n");
for (int n = 0; n < memories.size(); n++) {
var memory = memories.get(n);
System.out.println("Result " + (n + 1) + ":");
System.out.println(" URL: : " + memory.getMetadata().getId());
System.out.println(" Title : " + memory.getMetadata().getDescription());
System.out.println();
}
System.out.println("----------------------");
return null;
});
}
private static Mono<Void> storeMemoryAsync(Kernel kernel)
{
/* Store some data in the semantic memory.
*
* When using Azure Cognitive Search the data is automatically indexed on write.
*
* When using the combination of VolatileStore and Embedding generation, SK takes
* care of creating and storing the index
*/
return Flux.fromIterable(sampleData().entrySet())
.doFirst(() -> System.out.println("\nAdding some GitHub file URLs and their descriptions to the semantic memory."))
.map(entry -> {
System.out.println("Save '" + entry.getKey() + "' to memory.");
return kernel.getMemory().saveReferenceAsync(
MEMORY_COLLECTION_NAME,
entry.getValue(),
entry.getKey(),
"GitHub",
entry.getValue(),
null);
}
)
.mapNotNull(Mono::block)
.doFinally(signalType -> System.out.println("\n----------------------"))
.then();
}
private static Map<String, String> sampleData()
{
return Arrays.stream(new String[][] {
{"https://github.com/microsoft/semantic-kernel/blob/main/README.md", "README: Installation, getting started, and how to contribute"},
{"https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb", "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function"},
{"https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/00-getting-started.ipynb", "Jupyter notebook describing how to get started with the Semantic Kernel"},
{"https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT", "Sample demonstrating how to create a chat skill interfacing with ChatGPT"},
{"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/VolatileMemoryStore.cs", "C# class that defines a volatile embedding store"},
{"https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet/KernelHttpServer/README.md", "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4"},
{"https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/chat-summary-webapp-react/README.md", "README: README associated with a sample chat summary react-based webapp"},
}).collect(Collectors.toMap(element -> element[0], element -> element[1]));
}
}
@@ -32,23 +32,16 @@ public interface Kernel {
/**
* Reference to the engine rendering prompt templates
*
* @return
* @return Reference to the engine rendering prompt templates
*/
PromptTemplateEngine getPromptTemplateEngine();
/**
* Return the memory store used by the kernel.
* Get the SemanticTextMemory in use.
*
* @return the MemoryStore instance
* @return the SemanticTextMemory in use
*/
MemoryStore getMemoryStore();
/**
* Set the SemanticTextMemory to use.
*
* @param memory {@link SemanticTextMemory} instance
*/
void registerMemory(SemanticTextMemory memory);
SemanticTextMemory getMemory();
/**
* Run a pipeline composed of synchronous and asynchronous functions.
@@ -129,6 +122,7 @@ public interface Kernel {
@Nullable private KernelConfig kernelConfig = null;
@Nullable private PromptTemplateEngine promptTemplateEngine = null;
@Nullable private MemoryStore memoryStore = null;
@Nullable private SemanticTextMemory memory = null;
public Builder setKernelConfig(KernelConfig kernelConfig) {
this.kernelConfig = kernelConfig;
@@ -145,6 +139,11 @@ public interface Kernel {
return this;
}
public Builder withMemory(SemanticTextMemory memory) {
this.memory = memory;
return this;
}
public Kernel build() {
if (kernelConfig == null) {
throw new IllegalStateException("Must provide a kernel configuration");
@@ -152,7 +151,7 @@ public interface Kernel {
return BuildersSingleton.INST
.getKernelBuilder()
.build(kernelConfig, promptTemplateEngine, memoryStore);
.build(kernelConfig, promptTemplateEngine, memory, memoryStore);
}
}
@@ -160,6 +159,7 @@ public interface Kernel {
Kernel build(
KernelConfig kernelConfig,
@Nullable PromptTemplateEngine promptTemplateEngine,
@Nullable SemanticTextMemory memory,
@Nullable MemoryStore memoryStore);
}
}
@@ -6,10 +6,13 @@ import com.microsoft.semantickernel.chatcompletion.ChatCompletion;
import com.microsoft.semantickernel.orchestration.SKFunction;
import com.microsoft.semantickernel.textcompletion.TextCompletion;
import java.util.*;
import java.util.function.Function;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public final class KernelConfig {
@@ -73,6 +76,22 @@ public final class KernelConfig {
return this.chatCompletionServices.get(serviceId);
}
public Function<Kernel, EmbeddingGeneration<String,Float>> getTextEmbeddingGenerationServiceOrDefault(
@Nullable String serviceId) {
if (serviceId == null) {
serviceId = DEFAULT_SERVICE_ID;
}
if (!this.textEmbeddingGenerationServices.containsKey(serviceId)) {
throw new KernelException(
KernelException.ErrorCodes.ServiceNotFound,
"A embedding generation service id '" + serviceId + "' doesn't exist");
}
return this.textEmbeddingGenerationServices.get(serviceId);
}
public static class Builder {
private Map<String, Function<Kernel, TextCompletion>> textCompletionServices =
new HashMap<>();
@@ -4,6 +4,7 @@ package com.microsoft.semantickernel.builders;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import com.microsoft.semantickernel.chatcompletion.ChatCompletion;
import com.microsoft.semantickernel.memory.MemoryStore;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.orchestration.ContextVariables;
import com.microsoft.semantickernel.orchestration.SKContext;
@@ -47,6 +48,9 @@ public enum BuildersSingleton {
private static final String FALLBACK_CHAT_COMPLETION_BUILDER_CLASS =
"com.microsoft.semantickernel.connectors.ai.openai.chatcompletion.OpenAIChatCompletion$Builder";
private static final String FALLBACK_MEMORY_STORE_BUILDER_CLASS =
"com.microsoft.semantickernel.memory.VolatileMemoryStore$Builder";
private final FunctionBuilders functionBuilders;
private final Kernel.InternalBuilder kernelBuilder;
private final TextCompletion.Builder textCompletionBuilder;
@@ -57,8 +61,8 @@ public enum BuildersSingleton {
private final SKContext.Builder context;
private final PromptTemplateEngine.Builder promptTemplateEngine;
private final ChatCompletion.Builder chatCompletion;
private final SemanticTextMemory.Builder semanticTextMemoryBuilder;
private final MemoryStore.Builder memoryStoreBuilder;
BuildersSingleton() {
try {
@@ -108,6 +112,10 @@ public enum BuildersSingleton {
ServiceLoadUtil.findServiceLoader(
ChatCompletion.Builder.class, FALLBACK_CHAT_COMPLETION_BUILDER_CLASS);
memoryStoreBuilder =
ServiceLoadUtil.findServiceLoader(
MemoryStore.Builder.class, FALLBACK_MEMORY_STORE_BUILDER_CLASS);
} catch (Throwable e) {
Logger LOGGER = LoggerFactory.getLogger(BuildersSingleton.class);
LOGGER.error("Failed to discover Semantic Kernel Builders", e);
@@ -176,4 +184,8 @@ public enum BuildersSingleton {
public ChatCompletion.Builder getChatCompletion() {
return chatCompletion;
}
public MemoryStore.Builder getMemoryStoreBuilder() {
return memoryStoreBuilder;
}
}
@@ -5,6 +5,7 @@ import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.KernelConfig;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import com.microsoft.semantickernel.chatcompletion.ChatCompletion;
import com.microsoft.semantickernel.memory.MemoryStore;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.orchestration.ContextVariables;
import com.microsoft.semantickernel.orchestration.SKContext;
@@ -82,4 +83,8 @@ public class SKBuilders {
public static ChatCompletion.Builder chatCompletion() {
return BuildersSingleton.INST.getChatCompletion();
}
public static MemoryStore.Builder memoryStore() {
return BuildersSingleton.INST.getMemoryStoreBuilder();
}
}
@@ -146,4 +146,8 @@ public interface MemoryStore {
@Nonnull Embedding<? extends Number> embedding,
double minRelevanceScore,
boolean withEmbedding);
interface Builder {
MemoryStore build();
}
}
@@ -67,6 +67,10 @@ public interface SemanticTextMemory {
public Mono<MemoryQueryResult> getAsync(
@Nonnull String collection, @Nonnull String key, boolean withEmbedding);
@Deprecated
/**
* @deprecated No longer appears in the C# ISemanticTextMemory interface.
*/
SemanticTextMemory merge(MemoryQueryResult b);
/**
@@ -19,4 +19,24 @@
<modules>
<module>semantickernel-connectors</module>
</modules>
<properties>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
@@ -14,8 +14,33 @@
<artifactId>semantickernel-connectors</artifactId>
<repositories>
<repository>
<id>azure-sdk-for-java</id>
<url>https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-search-documents</artifactId>
<version>11.6.0-beta.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.semantickernel</groupId>
<artifactId>semantickernel-api</artifactId>
@@ -27,7 +27,7 @@ public class OpenAITextEmbeddingGeneration extends ClientBase
}
protected Mono<List<Embedding<Float>>> internalGenerateTextEmbeddingsAsync(List<String> data) {
EmbeddingsOptions options = new EmbeddingsOptions(data).setModel(getModelId());
EmbeddingsOptions options = new EmbeddingsOptions(data).setModel(getModelId()).setUser("default");
return getClient()
.getEmbeddings(getModelId(), options)
@@ -0,0 +1,359 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.connectors.memory.azurecognitivesearch;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.TokenCredential;
import com.azure.core.http.rest.Response;
import com.azure.search.documents.SearchAsyncClient;
import com.azure.search.documents.indexes.SearchIndexAsyncClient;
import com.azure.search.documents.indexes.SearchIndexClientBuilder;
import com.azure.search.documents.indexes.models.PrioritizedFields;
import com.azure.search.documents.indexes.models.SearchField;
import com.azure.search.documents.indexes.models.SearchIndex;
import com.azure.search.documents.indexes.models.SemanticConfiguration;
import com.azure.search.documents.indexes.models.SemanticField;
import com.azure.search.documents.indexes.models.SemanticSettings;
import com.azure.search.documents.models.IndexDocumentsOptions;
import com.azure.search.documents.models.IndexDocumentsResult;
import com.azure.search.documents.models.QueryLanguage;
import com.azure.search.documents.models.QueryType;
import com.azure.search.documents.models.SearchOptions;
import com.azure.search.documents.util.SearchPagedResponse;
import com.microsoft.semantickernel.exceptions.NotSupportedException;
import com.microsoft.semantickernel.memory.MemoryQueryResult;
import com.microsoft.semantickernel.memory.MemoryRecordMetadata;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import reactor.core.publisher.Mono;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
/**
* Semantic Memory implementation using Azure Cognitive Search. For more information about Azure
* Cognitive Search {@see https://learn.microsoft.com/azure/search/search-what-is-azure-search}
*/
public class AzureCognitiveSearchMemory implements SemanticTextMemory {
private static final String EMPTY_STRING = "";
private final SearchIndexAsyncClient _adminClient;
private final ConcurrentMap<String, SearchAsyncClient> _clientsByIndex =
new ConcurrentHashMap<>();
/**
* Create a new instance of semantic memory using Azure Cognitive Search.
*
* @param endpoint Azure Cognitive Search URI, e.g. {@code "https://contoso.search.windows.net"}
* @param apiKey The key used to authenticate HTTP requests sent to the Azure Cognitive Search service.
*/
public AzureCognitiveSearchMemory(String endpoint, String apiKey) {
AzureKeyCredential credentials = new AzureKeyCredential(apiKey);
this._adminClient =
new SearchIndexClientBuilder()
.endpoint(endpoint)
.credential(credentials)
.buildAsyncClient();
}
/**
* Create a new instance of semantic memory using Azure Cognitive Search.
*
* @param endpoint Azure Cognitive Search URI, e.g. {@code "https://contoso.search.windows.net"}
* @param credentials TokenCredential used to authorize requests sent to the Azure Cognitive Search service.
*/
public AzureCognitiveSearchMemory(String endpoint, TokenCredential credentials) {
this._adminClient =
new SearchIndexClientBuilder()
.endpoint(endpoint)
.credential(credentials)
.buildAsyncClient();
}
private AzureCognitiveSearchMemory(AzureCognitiveSearchMemory other) {
this._adminClient = other._adminClient;
}
@Override
public AzureCognitiveSearchMemory copy() {
return new AzureCognitiveSearchMemory(this);
}
@Override
public Mono<String> saveInformationAsync(
@Nonnull String collection,
@Nonnull String text,
@Nonnull String id,
@Nullable String description,
@Nullable String additionalMetadata) {
collection = normalizeIndexName(collection);
AzureCognitiveSearchRecord record =
new AzureCognitiveSearchRecord(
encodeId(id), text, description, additionalMetadata, null, false);
return this.upsertRecordAsync(collection, record);
}
@Override
public Mono<String> saveReferenceAsync(
@Nonnull String collection,
@Nonnull String text,
@Nonnull String externalId,
@Nonnull String externalSourceName,
@Nullable String description,
@Nullable String additionalMetadata) {
collection = normalizeIndexName(collection);
AzureCognitiveSearchRecord record =
new AzureCognitiveSearchRecord(
encodeId(externalId),
text,
description,
additionalMetadata,
externalSourceName,
true);
return this.upsertRecordAsync(collection, record);
}
@Override
public Mono<MemoryQueryResult> getAsync(
@Nonnull String collection, @Nonnull String key, boolean withEmbedding) {
SearchAsyncClient client = this.getSearchClient(normalizeIndexName(collection));
return client.getDocumentWithResponse(encodeId(key), AzureCognitiveSearchRecord.class, null)
.map(
(response) -> {
if (response.getStatusCode() == 404 || response.getValue() == null) {
throw new AzureCognitiveSearchMemoryException(
"Memory read returned null");
}
return new MemoryQueryResult(
toMemoryRecordMetadata(response.getValue()), 1);
});
}
@Override
public SemanticTextMemory merge(MemoryQueryResult b) {
throw new NotSupportedException("Deprecated");
}
@Override
public Mono<Void> removeAsync(@Nonnull String collection, @Nonnull String key) {
SearchAsyncClient client = this.getSearchClient(normalizeIndexName(collection));
List<AzureCognitiveSearchRecord> records =
Collections.singletonList(
new AzureCognitiveSearchRecord(encodeId(key), "", "", "", "", false));
return client.deleteDocumentsWithResponse(records, null).then();
}
private Collector<SearchPagedResponse, ?, List<MemoryQueryResult>> toMemoryQueryResultList =
Collector.of(
ArrayList::new,
(list, response) -> {
if (response.getStatusCode() == 404 || response.getValue() == null) {
throw new AzureCognitiveSearchMemoryException(
"Memory read returned null");
}
response.getValue().stream()
.map(
searchResult ->
new MemoryQueryResult(
toMemoryRecordMetadata(
searchResult.getDocument(
AzureCognitiveSearchRecord
.class)),
searchResult.getScore()))
.forEach(list::add);
},
(left, right) -> {
left.addAll(right);
return left;
});
@Override
public Mono<List<MemoryQueryResult>> searchAsync(
@Nonnull String collection,
@Nonnull String query,
int limit,
double minRelevanceScore,
boolean withEmbeddings) {
SearchAsyncClient client = this.getSearchClient(normalizeIndexName(collection));
SearchOptions options =
new SearchOptions()
.setQueryType(QueryType.SEMANTIC)
.setSemanticConfigurationName("default")
.setQueryLanguage(QueryLanguage.EN_US)
.setAnswersCount(limit);
return client.search(query, options).byPage().collect(toMemoryQueryResultList);
}
@Override
public Mono<List<String>> getCollectionsAsync() {
return this._adminClient
.listIndexes()
.map(SearchIndex::getName)
.collect(Collectors.toList());
}
// Index names cannot contain special chars. We use this rule to replace a few common ones
// with an underscore and reduce the chance of errors. If other special chars are used, we leave
// it
// to the service to throw an error.
// Note:
// - replacing chars introduces a small chance of conflicts, e.g. "the-user" and "the_user".
// - we should consider whether making this optional and leave it to the developer to handle.
//
// Java 8 does not support \s for whitespace, so we have to list them all.
private static String whitespace = " |\\t|\\n|\\x0B|\\f|\\r";
private static String s_replaceIndexNameSymbolsRegex = "[" + whitespace + "|\\|/|.|_|:]";
// Get a search client for the index specified.
// Note: the index might not exist, but we avoid checking everytime and the extra latency.
// <param name="indexName">Index name</param>
// <returns>Search client ready to read/write</returns>
private SearchAsyncClient getSearchClient(String indexName) {
// Search an available client from the local cache
String normalizeIndexName = normalizeIndexName(indexName);
return _clientsByIndex.computeIfAbsent(
indexName, k -> _adminClient.getSearchAsyncClient(normalizeIndexName));
}
// Create a new search index.
// <param name="indexName">Index name</param>
// <param name="cancellationToken">Task cancellation token</param>
// private Mono<SearchIndex> createIndexAsync(String indexName) {
private Mono<SearchIndex> createIndexAsync(String indexName) {
indexName = normalizeIndexName(indexName);
// TODO: Use FieldBuilder pending resolution of
// https://github.com/Azure/azure-sdk-for-java/issues/35584
// List<SearchField> fields =
// FieldBuilder.build(AzureCognitiveSearchRecord.class, null);
List<SearchField> fields = AzureCognitiveSearchRecord.searchFields();
SemanticField titleField = new SemanticField().setFieldName("Description");
List<SemanticField> prioritizedContentFields =
Arrays.asList(
new SemanticField().setFieldName("Text"),
new SemanticField().setFieldName("AdditionalMetadata"));
PrioritizedFields prioritizedFields =
new PrioritizedFields()
.setTitleField(titleField)
.setPrioritizedContentFields(prioritizedContentFields);
SemanticConfiguration semanticConfiguration =
new SemanticConfiguration("default", prioritizedFields);
SemanticSettings semanticSettings =
new SemanticSettings()
.setConfigurations(
Collections.singletonList(semanticConfiguration)
);
SearchIndex newIndex =
new SearchIndex(indexName, fields)
.setSemanticSettings(semanticSettings);
return this._adminClient.createIndex(newIndex);
}
@SuppressWarnings("unchecked")
private Mono<String> upsertRecordAsync(String indexName, AzureCognitiveSearchRecord record) {
SearchAsyncClient client = this.getSearchClient(indexName);
IndexDocumentsOptions throwOnAnyError =
new IndexDocumentsOptions().setThrowOnAnyError(true);
Function<Response<IndexDocumentsResult>, String> transform =
(response) -> {
if (response.getStatusCode() == 404
|| response.getValue() == null
|| response.getValue().getResults() == null
|| response.getValue().getResults().isEmpty()) {
throw new AzureCognitiveSearchMemoryException(
"Memory write returned null or an empty set");
}
return response.getValue().getResults().get(0).getKey();
};
return client.mergeOrUploadDocumentsWithResponse(Collections.singleton(record), throwOnAnyError)
.map(transform)
.onErrorResume(e ->
createIndexAsync(indexName)
.then(client.mergeOrUploadDocumentsWithResponse(
Collections.singleton(record), throwOnAnyError))
.map(transform)
);
}
private static MemoryRecordMetadata toMemoryRecordMetadata(AzureCognitiveSearchRecord data) {
return new MemoryRecordMetadata(
data.isReference(),
decodeId(data.getId()),
normalizeNullToEmptyString(data.getText()),
normalizeNullToEmptyString(data.getDescription()),
normalizeNullToEmptyString(data.getExternalSourceName()),
normalizeNullToEmptyString(data.getAdditionalMetadata()));
}
private static String normalizeNullToEmptyString(@Nullable String value) {
return value != null ? value : EMPTY_STRING;
}
// Normalize index name to match ACS rules.
// The method doesn't handle all the error scenarios, leaving it to the service
// to throw an error for edge cases not handled locally.
// <param name="indexName">Value to normalize</param>
// <returns>Normalized name</returns>
private static String normalizeIndexName(String indexName) {
if (indexName.length() > 128) {
throw new AzureCognitiveSearchMemoryException(
"The collection name is too long, it cannot exceed 128 chars");
}
indexName = indexName.toLowerCase(Locale.ROOT);
return indexName.replaceAll(s_replaceIndexNameSymbolsRegex, "-");
}
// ACS keys can contain only letters, digits, underscore, dash, equal sign, recommending
// to encode values with a URL-safe algorithm.
// <param name="realId">Original Id</param>
// <returns>Encoded id</returns>
private static String encodeId(@Nullable String realId) {
if (realId == null) {
return EMPTY_STRING;
}
byte[] bytes = Base64.getUrlEncoder().encode(realId.getBytes(StandardCharsets.UTF_8));
return new String(bytes, StandardCharsets.UTF_8);
}
private static String decodeId(@Nullable String encodedId) {
if (encodedId == null) {
return EMPTY_STRING;
}
byte[] bytes = Base64.getUrlDecoder().decode(encodedId.getBytes(StandardCharsets.UTF_8));
return new String(bytes, StandardCharsets.UTF_8);
}
}
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.connectors.memory.azurecognitivesearch;
import com.microsoft.semantickernel.SKException;
import javax.annotation.Nullable;
/** Exception thrown by the Azure Cognitive Search connector */
public class AzureCognitiveSearchMemoryException extends SKException {
/**
* Initializes a new instance of the {@code AzureCognitiveSearchMemoryException} class.
*
* @param message Exception message.
*/
public AzureCognitiveSearchMemoryException(@Nullable String message) {
super(message);
}
/**
* Initializes a new instance of the {@code AzureCognitiveSearchMemoryException} class.
*
* @param message Exception message.
* @param cause Cause of the exception, if any.
*/
public AzureCognitiveSearchMemoryException(
@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.connectors.memory.azurecognitivesearch;
import com.azure.search.documents.indexes.SearchableField;
import com.azure.search.documents.indexes.SimpleField;
import com.azure.search.documents.indexes.models.LexicalAnalyzerName;
import com.azure.search.documents.indexes.models.SearchField;
import com.azure.search.documents.indexes.models.SearchFieldDataType;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Arrays;
import java.util.List;
/**
* Azure Cognitive Search record and index definition. Note: once defined, index cannot be modified.
*/
public final class AzureCognitiveSearchRecord {
@SimpleField(isKey = true, isFilterable = false)
private final String id;
@SearchableField(analyzerName = "standard.lucene")
private final String text;
@SearchableField(analyzerName = "standard.lucene")
private final String description;
@SearchableField(analyzerName = "standard.lucene")
private final String additionalMetadata;
@SimpleField(isFilterable = false)
private final String externalSourceName;
@SimpleField(isFilterable = false)
private final boolean isReference;
@JsonCreator
public AzureCognitiveSearchRecord(
@JsonProperty("Id") String id,
@JsonProperty("Text") String text,
@JsonProperty("Description") String description,
@JsonProperty("AdditionalMetadata") String additionalMetadata,
@JsonProperty("ExternalSourceName") String externalSourceName,
@JsonProperty("Reference") boolean isReference) {
this.id = id;
this.text = text;
this.description = description;
this.additionalMetadata = additionalMetadata;
this.externalSourceName = externalSourceName;
this.isReference = isReference;
}
/**
* Record ID. The record is not filterable to save quota, also SK uses only semantic search.
*
* @return Record ID.
*/
public String getId() {
return id;
}
/**
* Content is stored here.
*
* @return Content is stored here, {@code null} if not set.
*/
public String getText() {
return text;
}
/**
* Optional description of the content, e.g. a title. This can be useful when indexing external
* data without pulling in the entire content.
*
* @return Optional description of the content, {@code null} if not set.
*/
public String getDescription() {
return description;
}
/**
* Additional metadata. Currently, this is a String where you could store serialized data as
* JSON. In future the design might change to allow storing named values and leverage filters.
*
* @return Additional metadata, {@code null} if not set.
*/
public String getAdditionalMetadata() {
return additionalMetadata;
}
/**
* Name of the external source, in cases where the content and the Id are referenced to external
* information.
*
* @return Name of the external source, in cases where the content and the Id are referenced to
* external information, {@code null} if not set.
*/
public String getExternalSourceName() {
return externalSourceName;
}
/**
* Whether the record references external information.
*
* @return {@code true} if the record references external information, {@code false} otherwise.
*/
public boolean isReference() {
return isReference;
}
// TODO: Potentially remove after resolution of
// https://github.com/Azure/azure-sdk-for-java/issues/35584
static List<SearchField> searchFields() {
return Arrays.asList(
new SearchField("Id", SearchFieldDataType.STRING).setKey(true).setFilterable(false),
new SearchField("Text", SearchFieldDataType.STRING)
.setSearchable(true)
.setAnalyzerName(LexicalAnalyzerName.EN_LUCENE),
new SearchField("Description", SearchFieldDataType.STRING)
.setSearchable(true)
.setAnalyzerName(LexicalAnalyzerName.EN_LUCENE),
new SearchField("AdditionalMetadata", SearchFieldDataType.STRING)
.setSearchable(true)
.setAnalyzerName(LexicalAnalyzerName.EN_LUCENE),
new SearchField("ExternalSourceName", SearchFieldDataType.STRING)
.setSearchable(true)
.setAnalyzerName(LexicalAnalyzerName.EN_LUCENE),
new SearchField("Reference", SearchFieldDataType.BOOLEAN).setFilterable(true)
);
}
// TODO: add one more field with the vector, float array, mark it as searchable
}
@@ -2,12 +2,14 @@
package com.microsoft.semantickernel;
import com.microsoft.semantickernel.ai.AIException;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import com.microsoft.semantickernel.builders.FunctionBuilders;
import com.microsoft.semantickernel.builders.SKBuilders;
import com.microsoft.semantickernel.chatcompletion.ChatCompletion;
import com.microsoft.semantickernel.coreskills.SkillImporter;
import com.microsoft.semantickernel.exceptions.NotSupportedException;
import com.microsoft.semantickernel.exceptions.SkillsNotFoundException;
import com.microsoft.semantickernel.memory.MemoryConfiguration;
import com.microsoft.semantickernel.memory.MemoryStore;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.orchestration.ContextVariables;
@@ -24,30 +26,27 @@ import com.microsoft.semantickernel.templateengine.DefaultPromptTemplateEngine;
import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
import com.microsoft.semantickernel.textcompletion.TextCompletion;
import jakarta.inject.Inject;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
public class DefaultKernel implements Kernel {
private final KernelConfig kernelConfig;
private final DefaultSkillCollection defaultSkillCollection;
private final PromptTemplateEngine promptTemplateEngine;
private MemoryStore memoryStore;
private SemanticTextMemory memory;
@Inject
public DefaultKernel(
KernelConfig kernelConfig,
PromptTemplateEngine promptTemplateEngine,
MemoryStore memoryStore) {
SemanticTextMemory memory) {
if (kernelConfig == null) {
throw new IllegalArgumentException();
}
@@ -55,7 +54,7 @@ public class DefaultKernel implements Kernel {
this.kernelConfig = kernelConfig;
this.promptTemplateEngine = promptTemplateEngine;
this.defaultSkillCollection = new DefaultSkillCollection();
this.memoryStore = memoryStore;
this.memory = memory;
kernelConfig.getSkills().forEach(this::registerSemanticFunction);
}
@@ -82,7 +81,17 @@ public class DefaultKernel implements Kernel {
}
return (T) factory.apply(this);
} else {
} else if (EmbeddingGeneration.class.isAssignableFrom(clazz)) {
Function<Kernel, EmbeddingGeneration<String,Float>> factory =
kernelConfig.getTextEmbeddingGenerationServiceOrDefault(serviceId);
if (factory == null) {
throw new KernelException(
KernelException.ErrorCodes.ServiceNotFound,
"No chat completion service available");
}
return (T) factory.apply(this);
}else {
// TODO correct exception
throw new NotSupportedException(
"The kernel service collection doesn't support the type " + clazz.getName());
@@ -200,13 +209,12 @@ public class DefaultKernel implements Kernel {
}
@Override
public MemoryStore getMemoryStore() {
return memoryStore;
public SemanticTextMemory getMemory() {
return memory;
}
@Override
public void registerMemory(@Nonnull SemanticTextMemory memory) {
throw new NotSupportedException("Not implemented");
this.memory = memory;
}
@Override
@@ -251,6 +259,7 @@ public class DefaultKernel implements Kernel {
public Kernel build(
KernelConfig kernelConfig,
@Nullable PromptTemplateEngine promptTemplateEngine,
@Nullable SemanticTextMemory memory,
@Nullable MemoryStore memoryStore) {
if (promptTemplateEngine == null) {
promptTemplateEngine = new DefaultPromptTemplateEngine();
@@ -262,7 +271,13 @@ public class DefaultKernel implements Kernel {
"It is required to set a kernelConfig to build a kernel");
}
return new DefaultKernel(kernelConfig, promptTemplateEngine, memoryStore);
DefaultKernel kernel = new DefaultKernel(kernelConfig, promptTemplateEngine, memory);
if (memoryStore != null) {
MemoryConfiguration.useMemory(kernel, memoryStore, null);
}
return kernel;
}
}
}
@@ -1,21 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.memory;
import com.microsoft.semantickernel.ai.embeddings.Embedding;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
import com.microsoft.semantickernel.exceptions.NotSupportedException;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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;
/// <summary>
/// Implementation of <see cref="ISemanticTextMemory"/>./>.
/// </summary>
@@ -137,19 +136,24 @@ public class DefaultSemanticTextMemory implements SemanticTextMemory {
@Nullable String description,
@Nullable String additionalMetadata) {
return Mono.error(new NotSupportedException("Pending implementation"));
// var embedding = await this._embeddingGenerator.GenerateEmbeddingAsync(text,
// cancellationToken: cancel);
// var data = MemoryRecord.ReferenceRecord(externalId: externalId, sourceName:
// externalSourceName, 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 this._storage.doesCollectionExistAsync(collection)
.map(exists -> {
if (!exists) {
return this._storage.createCollectionAsync(collection);
}
return Mono.empty();
})
.then(this._embeddingGenerator.generateEmbeddingsAsync(Collections.singletonList(text)))
.map(embeddings -> MemoryRecord.referenceRecord(
externalId,
externalSourceName,
description,
(Embedding<Float>) embeddings.iterator().next(),
additionalMetadata,
null,
null)
)
.flatMap(record -> this._storage.upsertAsync(collection, record));
}
@Override
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.memory;
import com.microsoft.semantickernel.DefaultKernel;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
@@ -31,7 +32,7 @@ public class MemoryConfiguration {
* embedding generation service
*/
public static void useMemory(
@Nonnull Kernel kernel,
@Nonnull DefaultKernel kernel,
@Nonnull MemoryStore storage,
@Nullable String embeddingsServiceId) {
Objects.requireNonNull(kernel);
@@ -61,7 +62,7 @@ public class MemoryConfiguration {
* storage} is {@code null}
*/
public static void useMemory(
@Nonnull Kernel kernel,
@Nonnull DefaultKernel kernel,
EmbeddingGeneration<String, Float> embeddingGenerator,
MemoryStore storage) {
Objects.requireNonNull(kernel);
@@ -3,11 +3,11 @@ package com.microsoft.semantickernel.memory;
import com.microsoft.semantickernel.ai.EmbeddingVector;
import com.microsoft.semantickernel.ai.embeddings.Embedding;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -20,8 +20,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.ToDoubleFunction;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
/** A simple volatile memory embeddings store. */
public class VolatileMemoryStore implements MemoryStore {
@@ -245,6 +243,15 @@ public class VolatileMemoryStore implements MemoryStore {
return collection;
}
public static class Builder implements MemoryStore.Builder {
public Builder() {}
@Override
public MemoryStore build() {
return new VolatileMemoryStore();
}
}
/*
protected boolean TryGetCollection(
String name,
+16 -4
View File
@@ -17,14 +17,14 @@
</Match>
<Match>
<Class name="com.microsoft.semantickernel.orchestration.planner.DefaultSequentialPlannerSKFunction"/>
<Field name="delegate"/>
<Class name="com.microsoft.semantickernel.Kernel$Builder"/>
<Method name="withMemoryStore"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.Kernel$Builder"/>
<Method name="withMemoryStore"/>
<Method name="withMemory"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
@@ -36,10 +36,22 @@
<Match>
<Class name="com.microsoft.semantickernel.DefaultKernel"/>
<Method name="getMemoryStore"/>
<Method name="getMemory"/>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.DefaultKernel"/>
<Field name="memory"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.DefaultKernel"/>
<Method name="registerMemory"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="com.microsoft.semantickernel.memory.DefaultSemanticTextMemory$Builder"/>
<Method name="setStorage"/>