Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import edu.kit.kastel.sdq.lissa.ratlr.cache.classifier.ClassifierCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.embedding.EmbeddingCacheKey;

/**
* Represents a key for caching operations in the LiSSA framework.
*
* The current types of cache keys are:
* <ul>
* <li>{@link edu.kit.kastel.sdq.lissa.ratlr.cache.EmbeddingCacheKey EmbeddingCacheKey} for caching embedding generation operations.</li>
* <li>{@link edu.kit.kastel.sdq.lissa.ratlr.cache.ClassifierCacheKey ClassifierCacheKey} for caching classification operations.</li>
* <li>{@link EmbeddingCacheKey EmbeddingCacheKey} for caching embedding generation operations.</li>
* <li>{@link ClassifierCacheKey ClassifierCacheKey} for caching classification operations.</li>
* </ul>
*/
public interface CacheKey {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,55 +79,33 @@ public static CacheManager getDefaultInstance() {
* @param parameters a list of parameters that define what makes a cache unique. E.g., the model name, temperature, and seed.
* @return A cache instance for the specified name
*/
public Cache getCache(Object origin, String[] parameters) {
public Cache getCache(Object origin, CacheParameter parameters) {
if (origin == null || parameters == null) {
throw new IllegalArgumentException("Origin and parameters must not be null");
}
for (String param : parameters) {
if (param == null) {
throw new IllegalArgumentException("Parameters must not contain null values");
}
}
String name = origin.getClass().getSimpleName() + "_" + String.join("_", parameters);
return getCache(name, true);
String name = origin.getClass().getSimpleName() + "_" + parameters.parameters();
return getCache(name);
}

/**
* Gets a cache instance for the specified name, optionally appending a file extension.
*
* @param name The name of the cache
* @param appendEnding Whether to append the .json extension to the cache name
* @return A cache instance for the specified name
*/
private Cache getCache(String name, boolean appendEnding) {
private Cache getCache(String name) {
name = name.replace(":", "__");

if (caches.containsKey(name)) {
return caches.get(name);
}

LocalCache localCache = new LocalCache(directoryOfCaches + "/" + name + (appendEnding ? ".json" : ""));
LocalCache localCache = new LocalCache(directoryOfCaches + "/" + name + ".json");
RedisCache cache = new RedisCache(localCache, DEFAULT_REPLACE_LOCAL_CACHE_ON_CONFLICT);
caches.put(name, cache);
return cache;
}

/**
* Gets a cache instance for an existing cache file.
*
* @param path The path to the existing cache file
* @param create Whether to create the cache file if it doesn't exist
* @return A cache instance for the specified file
* @throws IllegalArgumentException If the file doesn't exist (and create is false) or is a directory
*/
public Cache getCache(Path path, boolean create) {
path = directoryOfCaches.resolve(path.getFileName());
if ((!create && Files.notExists(path)) || Files.isDirectory(path)) {
throw new IllegalArgumentException("file does not exist or is a directory: " + path);
}
return getCache(path.getFileName().toString(), false);
}

/**
* Flushes all caches managed by this cache manager.
* This ensures that all pending changes are written to disk.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Licensed under MIT 2025. */
package edu.kit.kastel.sdq.lissa.ratlr.cache;

public interface CacheParameter {
/**
* Provide a unique string based on the actual cache parameters (for the file name of LocalCache)
* @return a unique string based on the cache parameters
*/
String parameters();
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* Licensed under MIT 2025. */
package edu.kit.kastel.sdq.lissa.ratlr.cache;
package edu.kit.kastel.sdq.lissa.ratlr.cache.classifier;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.LargeLanguageModelCacheMode;
import edu.kit.kastel.sdq.lissa.ratlr.utils.KeyGenerator;

/**
Expand All @@ -14,7 +16,7 @@
* <p>
* The key can be serialized to JSON for storage and retrieval from the cache.
* <p>
* Please always use the {@link #of(String, int, double, String)} method to create a new instance.
* Please always use the {@link #of(ClassifierCacheParameter, String)} method to create a new instance.
*
* @param model The identifier of the model used for the cached operation.
* @param seed The seed value used for randomization in the cached operation.
Expand All @@ -34,8 +36,13 @@ public record ClassifierCacheKey(
@JsonIgnore String localKey)
implements CacheKey {

public static ClassifierCacheKey of(String model, int seed, double temperature, String content) {
public static ClassifierCacheKey of(ClassifierCacheParameter cacheParameter, String content) {
return new ClassifierCacheKey(
model, seed, temperature, LargeLanguageModelCacheMode.CHAT, content, KeyGenerator.generateKey(content));
cacheParameter.modelName(),
cacheParameter.seed(),
cacheParameter.temperature(),
LargeLanguageModelCacheMode.CHAT,
content,
KeyGenerator.generateKey(content));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* Licensed under MIT 2025. */
package edu.kit.kastel.sdq.lissa.ratlr.cache.classifier;

import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheParameter;

public record ClassifierCacheParameter(String modelName, int seed, double temperature) implements CacheParameter {
@Override
public String parameters() {
if (temperature == 0.0) {
return String.join("_", modelName, String.valueOf(seed));
} else {
return String.join("_", modelName, String.valueOf(seed), String.valueOf(temperature));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* Licensed under MIT 2025. */
package edu.kit.kastel.sdq.lissa.ratlr.cache;
package edu.kit.kastel.sdq.lissa.ratlr.cache.embedding;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.LargeLanguageModelCacheMode;
import edu.kit.kastel.sdq.lissa.ratlr.utils.KeyGenerator;

/**
Expand All @@ -14,7 +16,7 @@
* <p>
* The key can be serialized to JSON for storage and retrieval from the cache.
* <p>
* Please always use the {@link #of(String, String)} method to create a new instance.
* Please always use the {@link #of(EmbeddingCacheParameter, String)} method to create a new instance.
*
* @param model The identifier of the model used for the cached operation.
* @param seed The seed value used for randomization in the cached operation (-1 for backward compatibility).
Expand All @@ -34,18 +36,23 @@
@JsonIgnore String localKey)
implements CacheKey {

public static EmbeddingCacheKey of(String model, String content) {
public static EmbeddingCacheKey of(EmbeddingCacheParameter cacheParameter, String content) {
return new EmbeddingCacheKey(
model, -1, -1, LargeLanguageModelCacheMode.EMBEDDING, content, KeyGenerator.generateKey(content));
cacheParameter.modelName(),
-1,
-1,
LargeLanguageModelCacheMode.EMBEDDING,
content,
KeyGenerator.generateKey(content));
}

/**
* Only use this method if you want to use a custom local key. You mostly do not want to do this. Only for special handling of embeddings.
* You should always prefer the {@link #of(String, String)} method.
* @deprecated please use {@link #of(String, String)} instead.
* You should always prefer the {@link #of(EmbeddingCacheParameter, String)} method.
* @deprecated please use {@link #of(EmbeddingCacheParameter, String)} instead.
*/
@Deprecated(forRemoval = false)
public static EmbeddingCacheKey ofRaw(String model, String content, String localKey) {

Check warning on line 55 in src/main/java/edu/kit/kastel/sdq/lissa/ratlr/cache/embedding/EmbeddingCacheKey.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=ArDoCo_LiSSA-RATLR&issues=AZsODIvoaWqPbcg_SDnM&open=AZsODIvoaWqPbcg_SDnM&pullRequest=48
return new EmbeddingCacheKey(model, -1, -1, LargeLanguageModelCacheMode.EMBEDDING, content, localKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* Licensed under MIT 2025. */
package edu.kit.kastel.sdq.lissa.ratlr.cache.embedding;

import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheParameter;

public record EmbeddingCacheParameter(String modelName) implements CacheParameter {
@Override
public String parameters() {
return modelName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.util.Base64;
import java.util.Map;

import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheParameter;
import edu.kit.kastel.sdq.lissa.ratlr.cache.classifier.ClassifierCacheParameter;
import edu.kit.kastel.sdq.lissa.ratlr.configuration.ModuleConfiguration;
import edu.kit.kastel.sdq.lissa.ratlr.utils.Environment;

Expand Down Expand Up @@ -302,14 +304,9 @@ private static ChatModel createOpenWebUIChatModel(String model, int seed, double
* This method is used to identify the cache uniquely.
*
* @return An array of strings representing the cache parameters
* @see edu.kit.kastel.sdq.lissa.ratlr.cache.CacheManager#getCache(Object, String[])
* @see edu.kit.kastel.sdq.lissa.ratlr.cache.CacheManager#getCache(Object, CacheParameter)
*/
public String[] getCacheParameters() {
if (temperature == 0.0) {
// Backwards compatibility with the old mode that did not have temperature
return new String[] {modelName(), String.valueOf(seed())};
} else {
return new String[] {modelName(), String.valueOf(seed()), String.valueOf(temperature())};
}
public ClassifierCacheParameter cacheParameters() {
return new ClassifierCacheParameter(modelName, seed, temperature);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import edu.kit.kastel.sdq.lissa.ratlr.cache.Cache;
import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheManager;
import edu.kit.kastel.sdq.lissa.ratlr.cache.ClassifierCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.classifier.ClassifierCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.configuration.ModuleConfiguration;
import edu.kit.kastel.sdq.lissa.ratlr.context.ContextStore;
import edu.kit.kastel.sdq.lissa.ratlr.knowledge.Element;
Expand Down Expand Up @@ -72,7 +72,7 @@ public class ReasoningClassifier extends Classifier {
public ReasoningClassifier(ModuleConfiguration configuration, ContextStore contextStore) {
super(ChatLanguageModelProvider.threads(configuration), contextStore);
this.provider = new ChatLanguageModelProvider(configuration);
this.cache = CacheManager.getDefaultInstance().getCache(this, provider.getCacheParameters());
this.cache = CacheManager.getDefaultInstance().getCache(this, provider.cacheParameters());
this.prompt = configuration.argumentAsStringByEnumIndex(
CLASSIFICATION_PROMPT_KEY,
0,
Expand Down Expand Up @@ -200,8 +200,7 @@ private String classifyIntern(Element source, Element target) {
messages.add(new UserMessage(request));

String messageString = getRepresentation(messages);
ClassifierCacheKey cacheKey =
ClassifierCacheKey.of(provider.modelName(), provider.seed(), provider.temperature(), messageString);
ClassifierCacheKey cacheKey = ClassifierCacheKey.of(provider.cacheParameters(), messageString);

String cachedResponse = cache.get(cacheKey, String.class);
if (cachedResponse != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import edu.kit.kastel.sdq.lissa.ratlr.cache.Cache;
import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheManager;
import edu.kit.kastel.sdq.lissa.ratlr.cache.ClassifierCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.classifier.ClassifierCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.configuration.ModuleConfiguration;
import edu.kit.kastel.sdq.lissa.ratlr.context.ContextStore;
import edu.kit.kastel.sdq.lissa.ratlr.knowledge.Element;
Expand All @@ -29,8 +29,7 @@ public class SimpleClassifier extends Classifier {
* The default template for classification requests.
* This template presents two artifacts and asks if they are related.
*/
private static final String DEFAULT_TEMPLATE =
"""
private static final String DEFAULT_TEMPLATE = """
Question: Here are two parts of software development artifacts.

{source_type}: '''{source_content}'''
Expand Down Expand Up @@ -75,7 +74,7 @@ public SimpleClassifier(ModuleConfiguration configuration, ContextStore contextS
super(ChatLanguageModelProvider.threads(configuration), contextStore);
this.provider = new ChatLanguageModelProvider(configuration);
this.template = configuration.argumentAsString(PROMPT_TEMPLATE_KEY, DEFAULT_TEMPLATE);
this.cache = CacheManager.getDefaultInstance().getCache(this, provider.getCacheParameters());
this.cache = CacheManager.getDefaultInstance().getCache(this, provider.cacheParameters());
this.llm = provider.createChatModel();
}

Expand Down Expand Up @@ -160,8 +159,7 @@ private String classifyIntern(Element source, Element target) {
.replace("{target_type}", target.getType())
.replace("{target_content}", target.getContent());

ClassifierCacheKey cacheKey =
ClassifierCacheKey.of(provider.modelName(), provider.seed(), provider.temperature(), request);
ClassifierCacheKey cacheKey = ClassifierCacheKey.of(provider.cacheParameters(), request);
String cachedResponse = cache.get(cacheKey, String.class);
if (cachedResponse != null) {
return cachedResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
import com.knuddels.jtokkit.api.Encoding;
import com.knuddels.jtokkit.api.EncodingRegistry;

import edu.kit.kastel.sdq.lissa.ratlr.cache.Cache;
import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheManager;
import edu.kit.kastel.sdq.lissa.ratlr.cache.EmbeddingCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.*;
import edu.kit.kastel.sdq.lissa.ratlr.cache.embedding.EmbeddingCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.embedding.EmbeddingCacheParameter;
import edu.kit.kastel.sdq.lissa.ratlr.context.ContextStore;
import edu.kit.kastel.sdq.lissa.ratlr.knowledge.Element;
import edu.kit.kastel.sdq.lissa.ratlr.utils.Futures;
Expand Down Expand Up @@ -45,6 +44,7 @@ abstract class CachedEmbeddingCreator extends EmbeddingCreator {
private final EmbeddingModel embeddingModel;
private final String rawNameOfModel;
private final int threads;
private final EmbeddingCacheParameter embeddingCacheParameter;

/**
* Creates a new cached embedding creator with the specified model and thread count.
Expand All @@ -56,7 +56,8 @@ abstract class CachedEmbeddingCreator extends EmbeddingCreator {
*/
protected CachedEmbeddingCreator(ContextStore contextStore, String model, int threads, String... params) {
super(contextStore);
this.cache = CacheManager.getDefaultInstance().getCache(this, new String[] {model});
this.embeddingCacheParameter = new EmbeddingCacheParameter(model);
this.cache = CacheManager.getDefaultInstance().getCache(this, embeddingCacheParameter);
this.embeddingModel = Objects.requireNonNull(createEmbeddingModel(model, params));
this.rawNameOfModel = model;
this.threads = Math.max(1, threads);
Expand Down Expand Up @@ -138,7 +139,7 @@ private List<float[]> calculateEmbeddingsSequential(List<Element> elements) {
private List<float[]> calculateEmbeddingsSequential(EmbeddingModel embeddingModel, List<Element> elements) {
List<float[]> embeddings = new ArrayList<>();
for (Element element : elements) {
embeddings.add(calculateFinalEmbedding(embeddingModel, cache, rawNameOfModel, element));
embeddings.add(calculateFinalEmbedding(embeddingModel, cache, embeddingCacheParameter, element));
}
return embeddings;
}
Expand Down Expand Up @@ -170,14 +171,17 @@ private List<float[]> calculateEmbeddingsSequential(EmbeddingModel embeddingMode
*
* @param embeddingModel The model to use for embedding generation
* @param cache The cache to use for storing and retrieving embeddings
* @param rawNameOfModel The name of the model being used
* @param embeddingCacheParameter The EmbeddingCacheParameter of the model being used
* @param element The element to create an embedding for
* @return The vector embedding of the element, either from cache or newly generated
*/
private static float[] calculateFinalEmbedding(
EmbeddingModel embeddingModel, Cache cache, String rawNameOfModel, Element element) {
EmbeddingModel embeddingModel,
Cache cache,
EmbeddingCacheParameter embeddingCacheParameter,
Element element) {

EmbeddingCacheKey cacheKey = EmbeddingCacheKey.of(rawNameOfModel, element.getContent());
EmbeddingCacheKey cacheKey = EmbeddingCacheKey.of(embeddingCacheParameter, element.getContent());

float[] cachedEmbedding = cache.get(cacheKey, float[].class);
if (cachedEmbedding != null) {
Expand All @@ -193,7 +197,8 @@ private static float[] calculateFinalEmbedding(
STATIC_LOGGER.error(
"Error while calculating embedding for .. try to fix ..: {}", element.getIdentifier());
// Probably the length was too long .. check that
return tryToFixWithLength(embeddingModel, cache, rawNameOfModel, cacheKey, element.getContent());
return tryToFixWithLength(
embeddingModel, cache, embeddingCacheParameter.modelName(), cacheKey, element.getContent());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import edu.kit.kastel.sdq.lissa.ratlr.cache.Cache;
import edu.kit.kastel.sdq.lissa.ratlr.cache.CacheManager;
import edu.kit.kastel.sdq.lissa.ratlr.cache.ClassifierCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.cache.classifier.ClassifierCacheKey;
import edu.kit.kastel.sdq.lissa.ratlr.classifier.ChatLanguageModelProvider;
import edu.kit.kastel.sdq.lissa.ratlr.configuration.ModuleConfiguration;
import edu.kit.kastel.sdq.lissa.ratlr.context.ContextStore;
Expand Down Expand Up @@ -64,7 +64,7 @@ public SummarizePreprocessor(ModuleConfiguration moduleConfiguration, ContextSto
this.template = moduleConfiguration.argumentAsString("template", "Summarize the following {type}: {content}");
this.provider = new ChatLanguageModelProvider(moduleConfiguration);
this.threads = ChatLanguageModelProvider.threads(moduleConfiguration);
this.cache = CacheManager.getDefaultInstance().getCache(this, provider.getCacheParameters());
this.cache = CacheManager.getDefaultInstance().getCache(this, provider.cacheParameters());
}

/**
Expand Down Expand Up @@ -107,8 +107,7 @@ public List<Element> preprocess(List<Artifact> artifacts) {
List<Callable<String>> tasks = new ArrayList<>();
for (String request : requests) {
tasks.add(() -> {
ClassifierCacheKey cacheKey =
ClassifierCacheKey.of(provider.modelName(), provider.seed(), provider.temperature(), request);
ClassifierCacheKey cacheKey = ClassifierCacheKey.of(provider.cacheParameters(), request);

String cachedResponse = cache.get(cacheKey, String.class);
if (cachedResponse != null) {
Expand Down
Loading