mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
v0 - semantic filter UI
This commit is contained in:
parent
44a8fa1479
commit
18309085af
5 changed files with 380 additions and 0 deletions
|
|
@ -0,0 +1,19 @@
|
|||
import { getMCPSemanticFilterSettings } from "@/components/networking";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createQueryKeys } from "../common/queryKeysFactory";
|
||||
import useAuthorized from "../useAuthorized";
|
||||
|
||||
const mcpSemanticFilterSettingsKeys = createQueryKeys(
|
||||
"mcpSemanticFilterSettings"
|
||||
);
|
||||
|
||||
export const useMCPSemanticFilterSettings = () => {
|
||||
const { accessToken } = useAuthorized();
|
||||
return useQuery<Record<string, any>>({
|
||||
queryKey: mcpSemanticFilterSettingsKeys.list({}),
|
||||
queryFn: async () => await getMCPSemanticFilterSettings(accessToken),
|
||||
enabled: !!accessToken,
|
||||
staleTime: 60 * 60 * 1000, // 1 hour
|
||||
gcTime: 60 * 60 * 1000, // 1 hour
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { updateMCPSemanticFilterSettings } from "@/components/networking";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { createQueryKeys } from "../common/queryKeysFactory";
|
||||
|
||||
const mcpSemanticFilterSettingsKeys = createQueryKeys(
|
||||
"mcpSemanticFilterSettings"
|
||||
);
|
||||
|
||||
export const useUpdateMCPSemanticFilterSettings = (accessToken: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (settings: Record<string, any>) => {
|
||||
if (!accessToken) {
|
||||
throw new Error("Access token is required");
|
||||
}
|
||||
return updateMCPSemanticFilterSettings(accessToken, settings);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: mcpSemanticFilterSettingsKeys.all,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
"use client";
|
||||
|
||||
import { useMCPSemanticFilterSettings } from "@/app/(dashboard)/hooks/mcpSemanticFilterSettings/useMCPSemanticFilterSettings";
|
||||
import { useUpdateMCPSemanticFilterSettings } from "@/app/(dashboard)/hooks/mcpSemanticFilterSettings/useUpdateMCPSemanticFilterSettings";
|
||||
import NotificationManager from "@/components/molecules/notifications_manager";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
InputNumber,
|
||||
Select,
|
||||
Skeleton,
|
||||
Slider,
|
||||
Space,
|
||||
Switch,
|
||||
Typography,
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import { QuestionCircleOutlined, CheckCircleOutlined, SaveOutlined } from "@ant-design/icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { fetchAvailableModels, ModelGroup } from "@/components/playground/llm_calls/fetch_models";
|
||||
|
||||
interface MCPSemanticFilterSettingsProps {
|
||||
accessToken: string | null;
|
||||
}
|
||||
|
||||
export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFilterSettingsProps) {
|
||||
const { data, isLoading, isError, error } = useMCPSemanticFilterSettings();
|
||||
const {
|
||||
mutate: updateSettings,
|
||||
isPending: isUpdating,
|
||||
error: updateError,
|
||||
} = useUpdateMCPSemanticFilterSettings(accessToken || "");
|
||||
const [form] = Form.useForm();
|
||||
const [saveSuccess, setSaveSuccess] = useState(false);
|
||||
const [isDirty, setIsDirty] = useState(false);
|
||||
const [embeddingModels, setEmbeddingModels] = useState<ModelGroup[]>([]);
|
||||
const [loadingModels, setLoadingModels] = useState(true);
|
||||
|
||||
const schema = data?.field_schema;
|
||||
const values = data?.values ?? {};
|
||||
|
||||
useEffect(() => {
|
||||
const loadEmbeddingModels = async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
setLoadingModels(true);
|
||||
const models = await fetchAvailableModels(accessToken);
|
||||
const embeddingOnly = models.filter((model) => model.mode === "embedding");
|
||||
setEmbeddingModels(embeddingOnly);
|
||||
} catch (error) {
|
||||
console.error("Error fetching embedding models:", error);
|
||||
} finally {
|
||||
setLoadingModels(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadEmbeddingModels();
|
||||
}, [accessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (values) {
|
||||
form.setFieldsValue({
|
||||
enabled: values.enabled ?? false,
|
||||
embedding_model: values.embedding_model ?? "text-embedding-3-small",
|
||||
top_k: values.top_k ?? 10,
|
||||
similarity_threshold: values.similarity_threshold ?? 0.3,
|
||||
});
|
||||
setIsDirty(false);
|
||||
}
|
||||
}, [values, form]);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const formValues = await form.validateFields();
|
||||
updateSettings(formValues, {
|
||||
onSuccess: () => {
|
||||
setIsDirty(false);
|
||||
setSaveSuccess(true);
|
||||
setTimeout(() => setSaveSuccess(false), 3000);
|
||||
NotificationManager.success(
|
||||
"Settings updated successfully. Changes will be applied across all pods within 10 seconds."
|
||||
);
|
||||
},
|
||||
onError: (error) => {
|
||||
NotificationManager.fromBackend(error);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Form validation failed:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!accessToken) {
|
||||
return (
|
||||
<div className="p-6 text-center text-gray-500">
|
||||
Please log in to configure semantic filter settings.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 800 }}>
|
||||
{isLoading ? (
|
||||
<Skeleton active />
|
||||
) : isError ? (
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not load MCP Semantic Filter settings"
|
||||
description={error instanceof Error ? error.message : undefined}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Alert
|
||||
type="info"
|
||||
message="Semantic Tool Filtering"
|
||||
description="Filter MCP tools semantically based on query relevance. This reduces context window size and improves tool selection accuracy. Click 'Save Settings' to apply changes across all pods (takes effect within 10 seconds)."
|
||||
showIcon
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
|
||||
{saveSuccess && (
|
||||
<Alert
|
||||
type="success"
|
||||
message="Settings saved successfully"
|
||||
icon={<CheckCircleOutlined />}
|
||||
showIcon
|
||||
closable
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{updateError && (
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not update settings"
|
||||
description={
|
||||
updateError instanceof Error ? updateError.message : undefined
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
disabled={isUpdating}
|
||||
onValuesChange={() => {
|
||||
setIsDirty(true);
|
||||
}}
|
||||
>
|
||||
<Card style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
name="enabled"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Enable Semantic Filtering</Typography.Text>
|
||||
<Tooltip title="When enabled, only the most relevant MCP tools will be included in requests based on semantic similarity">
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={isUpdating} />
|
||||
</Form.Item>
|
||||
|
||||
<Typography.Text type="secondary" style={{ display: "block", marginTop: -16, marginBottom: 16 }}>
|
||||
{schema?.properties?.enabled?.description}
|
||||
</Typography.Text>
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
name="embedding_model"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Embedding Model</Typography.Text>
|
||||
<Tooltip title="The model used to generate embeddings for semantic matching">
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
options={embeddingModels.map((model) => ({
|
||||
label: model.model_group,
|
||||
value: model.model_group,
|
||||
}))}
|
||||
placeholder={loadingModels ? "Loading models..." : "Select embedding model"}
|
||||
showSearch
|
||||
disabled={isUpdating || loadingModels}
|
||||
loading={loadingModels}
|
||||
notFoundContent={
|
||||
loadingModels ? "Loading..." : "No embedding models available"
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="top_k"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Top K Results</Typography.Text>
|
||||
<Tooltip title="Maximum number of tools to return after filtering">
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
style={{ width: "100%" }}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="similarity_threshold"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Similarity Threshold</Typography.Text>
|
||||
<Tooltip title="Minimum similarity score (0-1) for a tool to be included">
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
marks={{
|
||||
0: "0.0",
|
||||
0.3: "0.3",
|
||||
0.5: "0.5",
|
||||
0.7: "0.7",
|
||||
1: "1.0",
|
||||
}}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={handleSave}
|
||||
loading={isUpdating}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
Save Settings
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import MCPConnect from "./mcp_connect";
|
|||
import { mcpServerColumns } from "./mcp_server_columns";
|
||||
import { MCPServerView } from "./mcp_server_view";
|
||||
import { MCPServer, MCPServerProps, Team } from "./types";
|
||||
import MCPSemanticFilterSettings from "../Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings";
|
||||
|
||||
const { Text: AntdText, Title: AntdTitle } = Typography;
|
||||
const EDIT_OAUTH_UI_STATE_KEY = "litellm-mcp-oauth-edit-state";
|
||||
|
|
@ -302,6 +303,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
<div className="flex">
|
||||
<Tab>All Servers</Tab>
|
||||
<Tab>Connect</Tab>
|
||||
<Tab>Semantic Filter</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
|
|
@ -390,6 +392,9 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
<TabPanel>
|
||||
<MCPConnect />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<MCPSemanticFilterSettings accessToken={accessToken} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5398,6 +5398,73 @@ export const updateUISettings = async (accessToken: string, settings: any) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const getMCPSemanticFilterSettings = async (accessToken: string) => {
|
||||
/**
|
||||
* Get MCP semantic filter configuration
|
||||
*/
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/get/mcp_semantic_filter_settings`
|
||||
: `/get/mcp_semantic_filter_settings`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to get MCP semantic filter settings:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateMCPSemanticFilterSettings = async (
|
||||
accessToken: string,
|
||||
settings: Record<string, any>
|
||||
) => {
|
||||
/**
|
||||
* Update MCP semantic filter settings
|
||||
* Settings will be applied across all pods within 10 seconds
|
||||
*/
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/update/mcp_semantic_filter_settings`
|
||||
: `/update/mcp_semantic_filter_settings`;
|
||||
const response = await fetch(url, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to update MCP semantic filter settings:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getGuardrailsList = async (accessToken: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v2/guardrails/list` : `/v2/guardrails/list`;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue