diff --git a/litellm/proxy/management_endpoints/cache_settings_endpoints.py b/litellm/proxy/management_endpoints/cache_settings_endpoints.py index 9f45cb619aa..f4c4e26b582 100644 --- a/litellm/proxy/management_endpoints/cache_settings_endpoints.py +++ b/litellm/proxy/management_endpoints/cache_settings_endpoints.py @@ -287,7 +287,9 @@ async def get_cache_settings( # Derive redis_type for UI based on settings # UI uses redis_type to show/hide fields, backend only stores 'type' - if decrypted_settings.get("type") == "redis": + if decrypted_settings.get("type") == "redis-semantic": + decrypted_settings["redis_type"] = "semantic" + elif decrypted_settings.get("type") == "redis": if decrypted_settings.get("redis_startup_nodes"): decrypted_settings["redis_type"] = "cluster" elif decrypted_settings.get("sentinel_nodes"): diff --git a/tests/test_litellm/proxy/management_endpoints/test_cache_settings_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_cache_settings_endpoints.py index f4c6d4f8d15..e6a9be3e333 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_cache_settings_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_cache_settings_endpoints.py @@ -275,6 +275,38 @@ async def test_get_cache_settings_masks_password_bearing_url(): assert response.current_values["namespace"] == "ns" +@pytest.mark.asyncio +@pytest.mark.parametrize( + "stored_settings, expected_redis_type", + [ + ({"type": "redis-semantic", "host": "localhost"}, "semantic"), + ({"type": "redis", "redis_startup_nodes": [{"host": "h", "port": "7001"}]}, "cluster"), + ({"type": "redis", "sentinel_nodes": [["localhost", 26379]]}, "sentinel"), + ({"type": "redis", "host": "localhost"}, "node"), + ], +) +async def test_get_cache_settings_derives_redis_type_for_ui(stored_settings, expected_redis_type): + """The UI's Cache Type/Redis Deployment Type selectors round-trip on reload: a + redis-semantic config must surface as 'semantic' (not fall back to node), + and redis topologies must resolve to cluster/sentinel/node.""" + cache_row = MagicMock() + cache_row.cache_settings = json.dumps(stored_settings) + + mock_prisma = MagicMock() + mock_prisma.db.litellm_cacheconfig.find_unique = AsyncMock(return_value=cache_row) + + proxy_config = MagicMock() + proxy_config._decrypt_db_variables = MagicMock(side_effect=lambda variables_dict: dict(variables_dict)) + + with ( + patch("litellm.proxy.proxy_server.prisma_client", mock_prisma), + patch("litellm.proxy.proxy_server.proxy_config", proxy_config), + ): + response = await get_cache_settings(user_api_key_dict=_admin_auth()) + + assert response.current_values["redis_type"] == expected_redis_type + + class TestCacheSettingsManager: """Tests for CacheSettingsManager class""" diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index fe8f182c106..62cde1b30d5 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -45,11 +45,6 @@ "count": 1 } }, - "src/app/(dashboard)/caching/components/cache_settings/RedisTypeSelector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/caching/components/cache_settings/index.tsx": { "no-restricted-imports": { "count": 1 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/components/cache_settings/RedisTypeSelector.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/components/cache_settings/RedisTypeSelector.test.tsx deleted file mode 100644 index 9d4d5a6d425..00000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/components/cache_settings/RedisTypeSelector.test.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, expect, it } from "vitest"; -import RedisTypeSelector from "./RedisTypeSelector"; -import { render } from "@testing-library/react"; - -describe("RedisTypeSelector", () => { - it("should render the component", () => { - const { getAllByText } = render( - {}} />, - ); - expect(getAllByText(/Redis/i).length).toBeGreaterThan(0); - }); -}); 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 deleted file mode 100644 index fbca7ab5a97..00000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/components/cache_settings/RedisTypeSelector.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; -import { Select, SelectItem } from "@tremor/react"; - -interface RedisTypeSelectorProps { - redisType: string; - redisTypeDescriptions: Readonly>; - onTypeChange: (type: string) => void; -} - -const RedisTypeSelector: React.FC = ({ redisType, redisTypeDescriptions, onTypeChange }) => { - return ( -
- - -

- {redisTypeDescriptions[redisType] || "Select the type of Redis deployment you're using"} -

-
- ); -}; - -export default RedisTypeSelector; 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..2e5d9029d70 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 @@ -4,6 +4,10 @@ export type CacheFieldType = "string" | "password" | "integer" | "float" | "bool export type RedisType = "node" | "cluster" | "sentinel" | "semantic"; +export type CacheMode = "standard" | "semantic"; + +export type RedisDeploymentType = "node" | "cluster" | "sentinel"; + export type CacheSection = "connection" | "cluster" | "sentinel" | "semantic" | "ssl" | "cacheManagement" | "gcp"; export type CacheFieldRule = NonNullable[number]; @@ -19,13 +23,30 @@ export interface CacheField { readonly rules?: CacheFieldRule[]; } -export const REDIS_TYPES: readonly RedisType[] = ["node", "cluster", "sentinel", "semantic"]; +export const CACHE_MODES: readonly CacheMode[] = ["standard", "semantic"]; -export const REDIS_TYPE_DESCRIPTIONS: Readonly> = { +export const REDIS_DEPLOYMENT_TYPES: readonly RedisDeploymentType[] = ["node", "cluster", "sentinel"]; + +export const CACHE_MODE_LABELS: Readonly> = { + standard: "Standard (exact match)", + semantic: "Semantic (similarity-based)", +}; + +export const CACHE_MODE_DESCRIPTIONS: Readonly> = { + standard: "Exact-match caching that reuses a response only when a request matches a cached one exactly", + semantic: "Embedding-based caching that reuses responses for semantically similar prompts (single-node Redis only)", +}; + +export const REDIS_DEPLOYMENT_LABELS: Readonly> = { + node: "Node (Single Instance)", + cluster: "Cluster", + sentinel: "Sentinel", +}; + +export const REDIS_DEPLOYMENT_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 = { 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..632d0dd5cb8 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 @@ -4,10 +4,10 @@ import { Form } 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"; -import RedisTypeSelector from "./RedisTypeSelector"; +import CacheTypeSelector from "./CacheTypeSelector"; import CacheFieldSection from "./CacheFieldSection"; import { EmbeddingModelOption } from "./CacheFormField"; -import { REDIS_TYPES, REDIS_TYPE_DESCRIPTIONS, RedisType } from "./cacheSettingsFields"; +import { CacheMode, REDIS_DEPLOYMENT_TYPES, RedisDeploymentType, RedisType } from "./cacheSettingsFields"; import { buildCachePayload, buildInitialValues, CacheFormValues } from "./cacheSettingsUtils"; interface CacheSettingsProps { @@ -16,12 +16,26 @@ interface CacheSettingsProps { userID: string | null; } -const toRedisType = (value: unknown): RedisType => - REDIS_TYPES.includes(value as RedisType) ? (value as RedisType) : "node"; +interface CacheSelection { + cacheMode: CacheMode; + deploymentType: RedisDeploymentType; +} + +const toCacheSelection = (value: unknown): CacheSelection => { + if (value === "semantic") { + return { cacheMode: "semantic", deploymentType: "node" }; + } + const deploymentType = REDIS_DEPLOYMENT_TYPES.includes(value as RedisDeploymentType) + ? (value as RedisDeploymentType) + : "node"; + return { cacheMode: "standard", deploymentType }; +}; const CacheSettings: React.FC = ({ accessToken }) => { const [form] = Form.useForm(); - const [redisType, setRedisType] = useState("node"); + const [cacheMode, setCacheMode] = useState("standard"); + const [deploymentType, setDeploymentType] = useState("node"); + const redisType: RedisType = cacheMode === "semantic" ? "semantic" : deploymentType; const [embeddingModels, setEmbeddingModels] = useState([]); const [isTesting, setIsTesting] = useState(false); const [isSaving, setIsSaving] = useState(false); @@ -34,7 +48,9 @@ const CacheSettings: React.FC = ({ accessToken }) => { const data = (await getCacheSettingsCall(accessToken)) as { current_values?: Record }; const currentValues = data.current_values ?? {}; form.setFieldsValue(buildInitialValues(currentValues)); - setRedisType(toRedisType(currentValues.redis_type)); + const selection = toCacheSelection(currentValues.redis_type); + setCacheMode(selection.cacheMode); + setDeploymentType(selection.deploymentType); } catch (error) { console.error("Failed to load cache settings:", error); NotificationsManager.fromBackend("Failed to load cache settings"); @@ -132,10 +148,11 @@ const CacheSettings: React.FC = ({ accessToken }) => {

Configure Redis cache for LiteLLM

- setRedisType(toRedisType(type))} +