mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
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:
parent
bf02a4a47f
commit
b4f06bfe84
6 changed files with 45 additions and 23 deletions
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue