fix(ui): decouple Semantic from Redis Type dropdown in Cache Settings (#32621)

Semantic caching is a retrieval strategy, not a Redis deployment topology. This change:

- Removes 'Semantic' from the Redis Type dropdown (Node/Cluster/Sentinel)

- Adds a dedicated 'Enable Semantic Caching' toggle independent of Redis Type

- Updates buildCachePayload to accept semanticEnabled flag

- Auto-detects existing semantic configs and enables the toggle

- Updates all related tests

Fixes BerriAI/litellm#32621
This commit is contained in:
Rehan Sanjay 2026-07-10 04:07:05 +05:30
parent bf02a4a47f
commit b4f06bfe84
6 changed files with 45 additions and 23 deletions

View file

@ -15,7 +15,6 @@ const RedisTypeSelector: React.FC<RedisTypeSelectorProps> = ({ redisType, redisT
<SelectItem value="node">Node (Single Instance)</SelectItem>
<SelectItem value="cluster">Cluster</SelectItem>
<SelectItem value="sentinel">Sentinel</SelectItem>
<SelectItem value="semantic">Semantic</SelectItem>
</Select>
<p className="text-xs text-gray-500">
{redisTypeDescriptions[redisType] || "Select the type of Redis deployment you're using"}

View file

@ -2,7 +2,7 @@ import type { FormItemProps } from "antd";
export type CacheFieldType = "string" | "password" | "integer" | "float" | "boolean" | "list" | "model-select";
export type RedisType = "node" | "cluster" | "sentinel" | "semantic";
export type RedisType = "node" | "cluster" | "sentinel";
export type CacheSection = "connection" | "cluster" | "sentinel" | "semantic" | "ssl" | "cacheManagement" | "gcp";
@ -19,13 +19,12 @@ export interface CacheField {
readonly rules?: CacheFieldRule[];
}
export const REDIS_TYPES: readonly RedisType[] = ["node", "cluster", "sentinel", "semantic"];
export const REDIS_TYPES: readonly RedisType[] = ["node", "cluster", "sentinel"];
export const REDIS_TYPE_DESCRIPTIONS: Readonly<Record<RedisType, string>> = {
node: "Standard Redis node/single instance",
cluster: "Redis Cluster mode for high availability and horizontal scaling",
sentinel: "Redis Sentinel mode for high availability with automatic failover",
semantic: "Semantic caching that reuses responses for similar prompts",
};
const portRule: CacheFieldRule = {
@ -177,7 +176,7 @@ export const CACHE_FIELDS: readonly CacheField[] = [
type: "float",
section: "semantic",
helpText: "Similarity threshold for semantic cache",
redisType: "semantic",
redisType: null,
defaultValue: 0.8,
rules: [numberRule],
},
@ -187,7 +186,7 @@ export const CACHE_FIELDS: readonly CacheField[] = [
type: "model-select",
section: "semantic",
helpText: "Embedding model for semantic cache",
redisType: "semantic",
redisType: null,
},
{
name: "ssl",

View file

@ -68,14 +68,14 @@ describe("buildCachePayload", () => {
expect(payload).not.toHaveProperty("redis_startup_nodes");
});
it("should send type redis-semantic when saving a semantic cache", () => {
const payload = buildCachePayload("semantic", { similarity_threshold: 0.9 }, { forTesting: false });
it("should send type redis-semantic when saving with semantic caching enabled", () => {
const payload = buildCachePayload("node", { similarity_threshold: 0.9 }, { forTesting: false, semanticEnabled: true });
expect(payload.type).toBe("redis-semantic");
expect(payload.similarity_threshold).toBe(0.9);
});
it("should keep type redis when testing a semantic cache so the test endpoint accepts it", () => {
const payload = buildCachePayload("semantic", { similarity_threshold: 0.9 }, { forTesting: true });
it("should keep type redis when testing with semantic caching enabled so the test endpoint accepts it", () => {
const payload = buildCachePayload("node", { similarity_threshold: 0.9 }, { forTesting: true, semanticEnabled: true });
expect(payload.type).toBe("redis");
});

View file

@ -68,11 +68,20 @@ const saveValueForField = (field: CacheField, raw: CacheFormValue): CacheSavePay
export const buildCachePayload = (
redisType: RedisType,
values: CacheFormValues,
{ forTesting }: { forTesting: boolean },
{ forTesting, semanticEnabled = false }: { forTesting: boolean; semanticEnabled?: boolean },
): CacheSavePayload => {
const type = !forTesting && redisType === "semantic" ? "redis-semantic" : "redis";
const type = !forTesting && semanticEnabled ? "redis-semantic" : "redis";
const entries = CACHE_FIELDS.filter((field) => isFieldVisible(field, redisType)).flatMap((field) => {
const entries = CACHE_FIELDS.filter((field) => {
if (!isFieldVisible(field, redisType)) {
return false;
}
// Exclude semantic section fields when semantic caching is disabled
if (field.section === "semantic" && !semanticEnabled) {
return false;
}
return true;
}).flatMap((field) => {
const value = saveValueForField(field, values[field.name]);
return value === undefined ? [] : [[field.name, value] as const];
});

View file

@ -63,9 +63,9 @@ describe("CacheSettings", () => {
});
});
describe("when the redis type is semantic", () => {
it("should reveal the semantic fields", async () => {
getCacheSettingsCall.mockResolvedValue({ current_values: { redis_type: "semantic" } });
describe("when semantic caching is detected from existing config", () => {
it("should reveal the semantic fields when similarity_threshold is present", async () => {
getCacheSettingsCall.mockResolvedValue({ current_values: { similarity_threshold: 0.8 } });
renderSettings();
expect(await screen.findByText("Similarity Threshold")).toBeInTheDocument();
expect(screen.getByText("Embedding Model")).toBeInTheDocument();

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback } from "react";
import { Button, Accordion, AccordionHeader, AccordionBody } from "@tremor/react";
import { Form } from "antd";
import { Form, Switch } from "antd";
import { getCacheSettingsCall, testCacheConnectionCall, updateCacheSettingsCall } from "@/components/networking";
import { fetchAvailableModels, ModelGroup } from "@/components/llm_calls/fetch_models";
import NotificationsManager from "@/components/molecules/notifications_manager";
@ -25,6 +25,7 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
const [embeddingModels, setEmbeddingModels] = useState<EmbeddingModelOption[]>([]);
const [isTesting, setIsTesting] = useState<boolean>(false);
const [isSaving, setIsSaving] = useState<boolean>(false);
const [semanticEnabled, setSemanticEnabled] = useState<boolean>(false);
const loadCacheSettings = useCallback(async () => {
if (!accessToken) {
@ -35,6 +36,10 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
const currentValues = data.current_values ?? {};
form.setFieldsValue(buildInitialValues(currentValues));
setRedisType(toRedisType(currentValues.redis_type));
// Detect if existing config uses semantic caching
if (currentValues.redis_type === "semantic" || currentValues.similarity_threshold !== undefined || currentValues.redis_semantic_cache_embedding_model !== undefined) {
setSemanticEnabled(true);
}
} catch (error) {
console.error("Failed to load cache settings:", error);
NotificationsManager.fromBackend("Failed to load cache settings");
@ -81,7 +86,7 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
try {
const result = await testCacheConnectionCall(
accessToken,
buildCachePayload(redisType, values, { forTesting: true }),
buildCachePayload(redisType, values, { forTesting: true, semanticEnabled }),
);
if (result.status === "success") {
NotificationsManager.success("Cache connection test successful!");
@ -109,7 +114,7 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
setIsSaving(true);
try {
await updateCacheSettingsCall(accessToken, buildCachePayload(redisType, values, { forTesting: false }));
await updateCacheSettingsCall(accessToken, buildCachePayload(redisType, values, { forTesting: false, semanticEnabled }));
NotificationsManager.success("Cache settings updated successfully");
await loadCacheSettings();
} catch (error) {
@ -170,16 +175,26 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
</div>
)}
{redisType === "semantic" && (
<div className="pt-4 border-t border-gray-200">
<div className="pt-4 border-t border-gray-200">
<div className="flex items-center gap-3 mb-4">
<Switch
checked={semanticEnabled}
onChange={(checked) => setSemanticEnabled(checked)}
/>
<div>
<span className="text-sm font-medium text-gray-900">Enable Semantic Caching</span>
<p className="text-xs text-gray-500">Reuse responses for semantically similar prompts using embedding vectors</p>
</div>
</div>
{semanticEnabled && (
<CacheFieldSection
title="Semantic Configuration"
section="semantic"
redisType={redisType}
embeddingModels={embeddingModels}
/>
</div>
)}
)}
</div>
<Accordion className="mt-4">
<AccordionHeader>