diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/RedisTypeSelector.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/RedisTypeSelector.tsx index fbca7ab5a97..eb9f01c8543 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/RedisTypeSelector.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/RedisTypeSelector.tsx @@ -15,7 +15,6 @@ const RedisTypeSelector: React.FC = ({ redisType, redisT Node (Single Instance) Cluster Sentinel - Semantic

{redisTypeDescriptions[redisType] || "Select the type of Redis deployment you're using"} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsFields.ts b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsFields.ts index 1f5b566fc5f..cd76a44031e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsFields.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsFields.ts @@ -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> = { 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", diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsUtils.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsUtils.test.ts index 79f28a97842..55ebd525c57 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsUtils.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsUtils.test.ts @@ -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"); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsUtils.ts b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsUtils.ts index 088da21961c..172541f12fd 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsUtils.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/cacheSettingsUtils.ts @@ -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]; }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/index.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/index.test.tsx index 0f768372ad9..e1e34404396 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/index.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/index.test.tsx @@ -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(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/index.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/index.tsx index 4382769ae9c..42d77a2e11e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/index.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_settings/index.tsx @@ -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 = ({ accessToken }) => { const [embeddingModels, setEmbeddingModels] = useState([]); const [isTesting, setIsTesting] = useState(false); const [isSaving, setIsSaving] = useState(false); + const [semanticEnabled, setSemanticEnabled] = useState(false); const loadCacheSettings = useCallback(async () => { if (!accessToken) { @@ -35,6 +36,10 @@ const CacheSettings: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ accessToken }) => { )} - {redisType === "semantic" && ( -

+
+
+ setSemanticEnabled(checked)} + /> +
+ Enable Semantic Caching +

Reuse responses for semantically similar prompts using embedding vectors

+
+
+ {semanticEnabled && ( -
- )} + )} +