Skip to content

Commit c807672

Browse files
committed
feat(demo): add cache toggle with local and cloud cache options
Adds ComboBox selector for semantic cache type: - No Cache: Always call LLM - Local Cache: Redis-based semantic cache using SemanticCache - LangCache: Cloud-hosted semantic cache service Changes: - CacheType enum with NONE, LOCAL, LANGCACHE options - ServiceFactory initializes both local SemanticCache and LangCache - RAGService accepts CacheType parameter instead of boolean - ChatController uses ComboBox instead of CheckBox - CSS styling for cache ComboBox - Tests updated to use CacheType enum
1 parent 93dd2ba commit c807672

File tree

7 files changed

+142
-30
lines changed

7 files changed

+142
-30
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.redis.vl.demo.rag.model;
2+
3+
/**
4+
* Types of semantic caching available in the demo.
5+
*/
6+
public enum CacheType {
7+
/** No caching - always call LLM */
8+
NONE("No Cache", "Always call LLM, no caching"),
9+
10+
/** Local Redis-based semantic cache */
11+
LOCAL("Local Cache", "Redis-based semantic cache (local)"),
12+
13+
/** Cloud LangCache service */
14+
LANGCACHE("LangCache", "Cloud-hosted semantic cache service");
15+
16+
private final String displayName;
17+
private final String description;
18+
19+
CacheType(String displayName, String description) {
20+
this.displayName = displayName;
21+
this.description = description;
22+
}
23+
24+
public String getDisplayName() {
25+
return displayName;
26+
}
27+
28+
public String getDescription() {
29+
return description;
30+
}
31+
32+
@Override
33+
public String toString() {
34+
return displayName;
35+
}
36+
}

demos/rag-multimodal/src/main/java/com/redis/vl/demo/rag/service/RAGService.java

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.redis.vl.demo.rag.service;
22

3+
import com.redis.vl.demo.rag.model.CacheType;
34
import com.redis.vl.demo.rag.model.ChatMessage;
45
import com.redis.vl.demo.rag.model.LLMConfig;
6+
import com.redis.vl.extensions.cache.CacheHit;
57
import com.redis.vl.extensions.cache.LangCacheSemanticCache;
8+
import com.redis.vl.extensions.cache.SemanticCache;
69
import com.redis.vl.langchain4j.RedisVLContentRetriever;
710
import com.redis.vl.langchain4j.RedisVLDocumentStore;
811
import dev.langchain4j.data.image.Image;
@@ -42,6 +45,7 @@ public class RAGService {
4245
private final ChatLanguageModel chatModel;
4346
private final CostTracker costTracker;
4447
private final LLMConfig config;
48+
private final SemanticCache localCache;
4549
private final LangCacheSemanticCache langCache;
4650

4751
private static final String SYSTEM_PROMPT =
@@ -61,38 +65,54 @@ public class RAGService {
6165
* @param chatModel Chat language model for generation
6266
* @param costTracker Cost tracker for calculating costs
6367
* @param config LLM configuration
64-
* @param langCache LangCache wrapper for semantic caching (can be null)
68+
* @param localCache Local Redis-based semantic cache
69+
* @param langCache LangCache cloud service for semantic caching (can be null)
6570
*/
6671
public RAGService(
6772
RedisVLContentRetriever contentRetriever,
6873
RedisVLDocumentStore documentStore,
6974
ChatLanguageModel chatModel,
7075
CostTracker costTracker,
7176
LLMConfig config,
77+
SemanticCache localCache,
7278
LangCacheSemanticCache langCache) {
7379
this.contentRetriever = contentRetriever;
7480
this.documentStore = documentStore;
7581
this.chatModel = chatModel;
7682
this.costTracker = costTracker;
7783
this.config = config;
84+
this.localCache = localCache;
7885
this.langCache = langCache;
7986
}
8087

8188
/**
8289
* Queries the RAG system.
8390
*
8491
* @param userQuery User's question
85-
* @param useCache Whether to use LangCache for semantic caching
92+
* @param cacheType Type of caching to use (NONE, LOCAL, or LANGCACHE)
8693
* @return Assistant response with cost tracking
8794
* @throws IllegalArgumentException if userQuery is null or empty
8895
*/
89-
public ChatMessage query(String userQuery, boolean useCache) {
96+
public ChatMessage query(String userQuery, CacheType cacheType) {
9097
if (userQuery == null || userQuery.trim().isEmpty()) {
9198
throw new IllegalArgumentException("User query cannot be null or empty");
9299
}
93100

94-
// Check cache first if enabled
95-
if (useCache && langCache != null) {
101+
// Check cache first based on cache type
102+
if (cacheType == CacheType.LOCAL && localCache != null) {
103+
System.out.println("→ Checking Local Cache for: " + userQuery);
104+
Optional<CacheHit> cacheHit = localCache.check(userQuery);
105+
106+
if (cacheHit.isPresent()) {
107+
String cachedResponse = cacheHit.get().getResponse();
108+
int tokenCount = costTracker.countTokens(cachedResponse);
109+
110+
System.out.println("✓ Local Cache HIT for query: " + userQuery);
111+
return ChatMessage.assistant(cachedResponse, tokenCount, 0.0, config.model(), true);
112+
} else {
113+
System.out.println("✗ Local Cache MISS for query: " + userQuery);
114+
}
115+
} else if (cacheType == CacheType.LANGCACHE && langCache != null) {
96116
try {
97117
System.out.println("→ Checking LangCache for: " + userQuery);
98118
List<Map<String, Object>> cacheHits = langCache.check(userQuery, null, 1, null, null, 0.9f);
@@ -103,10 +123,10 @@ public ChatMessage query(String userQuery, boolean useCache) {
103123
String cachedResponse = (String) hit.get("response");
104124
int tokenCount = costTracker.countTokens(cachedResponse);
105125

106-
System.out.println("✓ Cache HIT for query: " + userQuery);
126+
System.out.println("✓ LangCache HIT for query: " + userQuery);
107127
return ChatMessage.assistant(cachedResponse, tokenCount, 0.0, config.model(), true);
108128
} else {
109-
System.out.println("✗ Cache MISS for query: " + userQuery);
129+
System.out.println("✗ LangCache MISS for query: " + userQuery);
110130
}
111131
} catch (IOException e) {
112132
System.err.println("LangCache check failed: " + e.getMessage());
@@ -201,12 +221,16 @@ public ChatMessage query(String userQuery, boolean useCache) {
201221
Response<AiMessage> response = chatModel.generate(messages);
202222
String responseText = response.content().text();
203223

204-
// 4. Store in cache if enabled
205-
if (useCache && langCache != null) {
224+
// 4. Store in cache based on cache type
225+
if (cacheType == CacheType.LOCAL && localCache != null) {
226+
System.out.println("→ Storing to Local Cache: " + userQuery);
227+
localCache.store(userQuery, responseText);
228+
System.out.println("✓ Cached response in Local Cache for query: " + userQuery);
229+
} else if (cacheType == CacheType.LANGCACHE && langCache != null) {
206230
try {
207231
System.out.println("→ Storing to LangCache: " + userQuery);
208232
String entryId = langCache.store(userQuery, responseText, null);
209-
System.out.println("✓ Cached response for query: " + userQuery + " (entry_id: " + entryId + ")");
233+
System.out.println("✓ Cached response in LangCache for query: " + userQuery + " (entry_id: " + entryId + ")");
210234
} catch (IOException e) {
211235
System.err.println("LangCache store failed: " + e.getMessage());
212236
e.printStackTrace();

demos/rag-multimodal/src/main/java/com/redis/vl/demo/rag/service/ServiceFactory.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import com.redis.vl.demo.rag.config.AppConfig;
44
import com.redis.vl.demo.rag.model.LLMConfig;
55
import com.redis.vl.extensions.cache.LangCacheSemanticCache;
6+
import com.redis.vl.extensions.cache.SemanticCache;
67
import com.redis.vl.index.SearchIndex;
78
import com.redis.vl.langchain4j.RedisVLContentRetriever;
89
import com.redis.vl.langchain4j.RedisVLDocumentStore;
910
import com.redis.vl.langchain4j.RedisVLEmbeddingStore;
1011
import com.redis.vl.schema.IndexSchema;
12+
import com.redis.vl.utils.vectorize.LangChain4JVectorizer;
1113
import dev.langchain4j.model.chat.ChatLanguageModel;
1214
import dev.langchain4j.model.embedding.EmbeddingModel;
1315
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
@@ -34,6 +36,7 @@ public class ServiceFactory {
3436
private EmbeddingModel embeddingModel;
3537
private CostTracker costTracker;
3638
private LangCacheSemanticCache langCache;
39+
private SemanticCache localCache;
3740

3841
/**
3942
* Initializes all services with default configuration.
@@ -59,6 +62,17 @@ public void initialize(String redisHost, int redisPort) throws Exception {
5962
embeddingStore = new RedisVLEmbeddingStore(searchIndex);
6063
documentStore = new RedisVLDocumentStore(jedis, "rag:docs:");
6164

65+
// Initialize local SemanticCache (always available)
66+
LangChain4JVectorizer vectorizer = new LangChain4JVectorizer(
67+
"all-minilm-l6-v2", embeddingModel, VECTOR_DIM);
68+
localCache = new SemanticCache.Builder()
69+
.name("rag_local_cache")
70+
.redisClient(jedis)
71+
.vectorizer(vectorizer)
72+
.distanceThreshold(0.1f) // 90% similarity for cache hit
73+
.build();
74+
System.out.println("Local SemanticCache initialized");
75+
6276
// Initialize LangCache if enabled
6377
AppConfig config = AppConfig.getInstance();
6478
if (config.isLangCacheEnabled()) {
@@ -143,7 +157,7 @@ public RAGService createRAGService(LLMConfig config) {
143157
// Create chat model based on provider
144158
ChatLanguageModel chatModel = createChatModel(config);
145159

146-
return new RAGService(retriever, documentStore, chatModel, costTracker, config, langCache);
160+
return new RAGService(retriever, documentStore, chatModel, costTracker, config, localCache, langCache);
147161
}
148162

149163
/**

demos/rag-multimodal/src/main/java/com/redis/vl/demo/rag/ui/ChatController.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.redis.vl.demo.rag.ui;
22

33
import com.redis.vl.demo.rag.config.AppConfig;
4+
import com.redis.vl.demo.rag.model.CacheType;
45
import com.redis.vl.demo.rag.model.ChatMessage;
56
import com.redis.vl.demo.rag.model.LLMConfig;
67
import com.redis.vl.demo.rag.service.JTokKitCostTracker;
@@ -29,7 +30,7 @@ public class ChatController extends BorderPane {
2930
private final ListView<ChatMessage> messageListView;
3031
private final TextField inputField;
3132
private final Button sendButton;
32-
private final CheckBox cacheCheckBox;
33+
private final ComboBox<CacheType> cacheComboBox;
3334
private final Label statusLabel;
3435
private final Label providerLabel;
3536
private final Label modelLabel;
@@ -105,15 +106,17 @@ protected void updateItem(ChatMessage item, boolean empty) {
105106

106107
Separator configSeparator = new Separator();
107108

108-
// LangCache section
109+
// Cache section
109110
Label cacheTitle = new Label("Semantic Cache");
110111
cacheTitle.getStyleClass().add("section-title");
111112

112-
cacheCheckBox = new CheckBox("Enable LangCache");
113-
cacheCheckBox.setSelected(config.isLangCacheEnabled());
114-
cacheCheckBox.getStyleClass().add("cache-checkbox");
113+
cacheComboBox = new ComboBox<>();
114+
cacheComboBox.getItems().addAll(CacheType.values());
115+
cacheComboBox.setValue(config.isLangCacheEnabled() ? CacheType.LANGCACHE : CacheType.NONE);
116+
cacheComboBox.getStyleClass().add("cache-combobox");
117+
cacheComboBox.setMaxWidth(Double.MAX_VALUE);
115118

116-
Label cacheInfo = new Label("Toggle to demonstrate\ncost savings with\nsemantic caching");
119+
Label cacheInfo = new Label("Select caching mode:\n• No Cache: Always call LLM\n• Local: Redis-based cache\n• LangCache: Cloud cache");
117120
cacheInfo.getStyleClass().add("info-text");
118121
cacheInfo.setWrapText(true);
119122

@@ -142,7 +145,7 @@ protected void updateItem(ChatMessage item, boolean empty) {
142145

143146
rightPanel.getChildren().addAll(
144147
configTitle, providerLabel, modelLabel, loadedPdfLabel, configSeparator,
145-
cacheTitle, cacheCheckBox, cacheInfo, cacheSeparator,
148+
cacheTitle, cacheComboBox, cacheInfo, cacheSeparator,
146149
actionsTitle, uploadPdfButton, actionsSeparator,
147150
spacer,
148151
statusTitle, statusLabel
@@ -209,8 +212,8 @@ private void sendMessage() {
209212
executor.submit(
210213
() -> {
211214
try {
212-
boolean useCache = cacheCheckBox.isSelected();
213-
ChatMessage response = ragService.query(userInput, useCache);
215+
CacheType cacheType = cacheComboBox.getValue();
216+
ChatMessage response = ragService.query(userInput, cacheType);
214217

215218
Platform.runLater(
216219
() -> {

demos/rag-multimodal/src/main/resources/styles/app.css

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,36 @@
337337
-fx-background-color: #E4E6EB;
338338
-fx-padding: 0 2px;
339339
}
340+
341+
/* Cache ComboBox */
342+
.cache-combobox {
343+
-fx-background-color: #FFFFFF;
344+
-fx-border-color: #CED0D4;
345+
-fx-border-width: 1px;
346+
-fx-border-radius: 4px;
347+
-fx-background-radius: 4px;
348+
-fx-padding: 8px 12px;
349+
-fx-font-size: 13px;
350+
}
351+
352+
.cache-combobox:hover {
353+
-fx-border-color: #1877F2;
354+
}
355+
356+
.cache-combobox .list-cell {
357+
-fx-padding: 8px 12px;
358+
}
359+
360+
.cache-combobox-popup .list-view {
361+
-fx-background-color: #FFFFFF;
362+
-fx-border-color: #CED0D4;
363+
}
364+
365+
.cache-combobox-popup .list-cell:hover {
366+
-fx-background-color: #F0F2F5;
367+
}
368+
369+
.cache-combobox-popup .list-cell:selected {
370+
-fx-background-color: #E7F3FF;
371+
-fx-text-fill: #1877F2;
372+
}

demos/rag-multimodal/src/test/java/com/redis/vl/demo/rag/service/MultimodalRAGIntegrationTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.junit.jupiter.api.Assertions.*;
44

55
import com.redis.vl.BaseIntegrationTest;
6+
import com.redis.vl.demo.rag.model.CacheType;
67
import com.redis.vl.demo.rag.model.ChatMessage;
78
import com.redis.vl.demo.rag.model.LLMConfig;
89
import com.redis.vl.index.SearchIndex;
@@ -126,7 +127,7 @@ void setUp() throws Exception {
126127

127128
JTokKitCostTracker costTracker = new JTokKitCostTracker();
128129

129-
ragService = new RAGService(retriever, documentStore, chatModel, costTracker, config, null);
130+
ragService = new RAGService(retriever, documentStore, chatModel, costTracker, config, null, null);
130131

131132
// Ingest Attention.pdf
132133
File attentionPdf =
@@ -163,7 +164,7 @@ void testVisionModelReceivesAndProcessesImages() {
163164
String query = "Describe the Transformer architecture diagram. What are the main components?";
164165

165166
// When: Query the RAG system
166-
ChatMessage response = ragService.query(query, false);
167+
ChatMessage response = ragService.query(query, CacheType.NONE);
167168

168169
// Then: Response should mention visual elements from the diagram
169170
String responseText = response.content().toLowerCase();
@@ -203,7 +204,7 @@ void testImagesRetrievedFromDocumentStore() {
203204
String query = "What does the attention visualization show?";
204205

205206
// When: Query the RAG system
206-
ChatMessage response = ragService.query(query, false);
207+
ChatMessage response = ragService.query(query, CacheType.NONE);
207208

208209
// Then: Response should indicate it saw the actual visualization image
209210
String responseText = response.content().toLowerCase();
@@ -232,7 +233,7 @@ void testGPT4VisionDescribesVisualElements() {
232233
String query = "How many attention heads are shown in the Multi-Head Attention diagram?";
233234

234235
// When: Query the RAG system
235-
ChatMessage response = ragService.query(query, false);
236+
ChatMessage response = ragService.query(query, CacheType.NONE);
236237

237238
// Then: Response should provide specific count from diagram
238239
String responseText = response.content().toLowerCase();

demos/rag-multimodal/src/test/java/com/redis/vl/demo/rag/service/RAGServiceTest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.mockito.ArgumentMatchers.any;
55
import static org.mockito.Mockito.*;
66

7+
import com.redis.vl.demo.rag.model.CacheType;
78
import com.redis.vl.demo.rag.model.ChatMessage;
89
import com.redis.vl.demo.rag.model.LLMConfig;
910
import com.redis.vl.langchain4j.RedisVLContentRetriever;
@@ -36,7 +37,7 @@ class RAGServiceTest {
3637
void setUp() {
3738
MockitoAnnotations.openMocks(this);
3839
config = LLMConfig.defaultConfig(LLMConfig.Provider.OPENAI, "test-key");
39-
ragService = new RAGService(contentRetriever, documentStore, chatModel, costTracker, config, null);
40+
ragService = new RAGService(contentRetriever, documentStore, chatModel, costTracker, config, null, null);
4041
}
4142

4243
@Test
@@ -61,7 +62,7 @@ void testQueryWithContext() {
6162
.thenReturn(0.0001);
6263

6364
// When
64-
ChatMessage result = ragService.query(userQuery, false);
65+
ChatMessage result = ragService.query(userQuery, CacheType.NONE);
6566

6667
// Then
6768
assertNotNull(result);
@@ -89,7 +90,7 @@ void testQueryWithCache() {
8990
when(costTracker.calculateCost(any(), any(), anyInt())).thenReturn(0.0);
9091

9192
// When
92-
ChatMessage result = ragService.query(userQuery, true);
93+
ChatMessage result = ragService.query(userQuery, CacheType.LANGCACHE);
9394

9495
// Then
9596
assertNotNull(result);
@@ -113,7 +114,7 @@ void testQueryWithEmptyContext() {
113114
when(costTracker.calculateCost(any(), any(), anyInt())).thenReturn(0.00005);
114115

115116
// When
116-
ChatMessage result = ragService.query(userQuery, false);
117+
ChatMessage result = ragService.query(userQuery, CacheType.NONE);
117118

118119
// Then
119120
assertNotNull(result);
@@ -123,12 +124,12 @@ void testQueryWithEmptyContext() {
123124
@Test
124125
void testQueryWithNullInput() {
125126
// When/Then
126-
assertThrows(IllegalArgumentException.class, () -> ragService.query(null, false));
127+
assertThrows(IllegalArgumentException.class, () -> ragService.query(null, CacheType.NONE));
127128
}
128129

129130
@Test
130131
void testQueryWithEmptyInput() {
131132
// When/Then
132-
assertThrows(IllegalArgumentException.class, () -> ragService.query("", false));
133+
assertThrows(IllegalArgumentException.class, () -> ragService.query("", CacheType.NONE));
133134
}
134135
}

0 commit comments

Comments
 (0)