mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(ui): wire up cache type selectors and semantic round-trip
Backend derivation, field/type definitions, index state, and lint baseline for the Cache Type / Redis Deployment Type split.
This commit is contained in:
parent
3b89c594fa
commit
8dcc631dbb
7 changed files with 86 additions and 58 deletions
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<RedisTypeSelector redisType="redis" redisTypeDescriptions={{}} onTypeChange={() => {}} />,
|
||||
);
|
||||
expect(getAllByText(/Redis/i).length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import React from "react";
|
||||
import { Select, SelectItem } from "@tremor/react";
|
||||
|
||||
interface RedisTypeSelectorProps {
|
||||
redisType: string;
|
||||
redisTypeDescriptions: Readonly<Record<string, string>>;
|
||||
onTypeChange: (type: string) => void;
|
||||
}
|
||||
|
||||
const RedisTypeSelector: React.FC<RedisTypeSelectorProps> = ({ redisType, redisTypeDescriptions, onTypeChange }) => {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Redis Type</label>
|
||||
<Select value={redisType} onValueChange={onTypeChange}>
|
||||
<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"}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RedisTypeSelector;
|
||||
|
|
@ -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<FormItemProps["rules"]>[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<Record<RedisType, string>> = {
|
||||
export const REDIS_DEPLOYMENT_TYPES: readonly RedisDeploymentType[] = ["node", "cluster", "sentinel"];
|
||||
|
||||
export const CACHE_MODE_LABELS: Readonly<Record<CacheMode, string>> = {
|
||||
standard: "Standard (exact match)",
|
||||
semantic: "Semantic (similarity-based)",
|
||||
};
|
||||
|
||||
export const CACHE_MODE_DESCRIPTIONS: Readonly<Record<CacheMode, string>> = {
|
||||
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<Record<RedisDeploymentType, string>> = {
|
||||
node: "Node (Single Instance)",
|
||||
cluster: "Cluster",
|
||||
sentinel: "Sentinel",
|
||||
};
|
||||
|
||||
export const REDIS_DEPLOYMENT_DESCRIPTIONS: Readonly<Record<RedisDeploymentType, 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 = {
|
||||
|
|
|
|||
|
|
@ -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<CacheSettingsProps> = ({ accessToken }) => {
|
||||
const [form] = Form.useForm<CacheFormValues>();
|
||||
const [redisType, setRedisType] = useState<RedisType>("node");
|
||||
const [cacheMode, setCacheMode] = useState<CacheMode>("standard");
|
||||
const [deploymentType, setDeploymentType] = useState<RedisDeploymentType>("node");
|
||||
const redisType: RedisType = cacheMode === "semantic" ? "semantic" : deploymentType;
|
||||
const [embeddingModels, setEmbeddingModels] = useState<EmbeddingModelOption[]>([]);
|
||||
const [isTesting, setIsTesting] = useState<boolean>(false);
|
||||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
|
|
@ -34,7 +48,9 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
|
|||
const data = (await getCacheSettingsCall(accessToken)) as { current_values?: Record<string, unknown> };
|
||||
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<CacheSettingsProps> = ({ accessToken }) => {
|
|||
<p className="text-xs text-gray-500 mt-1">Configure Redis cache for LiteLLM</p>
|
||||
</div>
|
||||
|
||||
<RedisTypeSelector
|
||||
redisType={redisType}
|
||||
redisTypeDescriptions={REDIS_TYPE_DESCRIPTIONS}
|
||||
onTypeChange={(type) => setRedisType(toRedisType(type))}
|
||||
<CacheTypeSelector
|
||||
cacheMode={cacheMode}
|
||||
deploymentType={deploymentType}
|
||||
onCacheModeChange={setCacheMode}
|
||||
onDeploymentTypeChange={setDeploymentType}
|
||||
/>
|
||||
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue