From c0bc45224cab03be2c3640f348202cb68222643d Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Sat, 19 Sep 2026 16:00:11 -0700 Subject: [PATCH] feat(ui): report whether the serving proxy has applied the interception setting The stored flag is the cluster's desired state and is what the form saves, so it cannot also stand as proof that this process activated the callback: a pod that lagged or failed to apply it would still read as on. Report the process's own registration as a separate read-only field and warn on the page when the two disagree, so a failed activation is visible instead of only logged. --- .../proxy_setting_endpoints.py | 23 ++++++++++++- .../test_proxy_setting_endpoints.py | 32 +++++++++++++++++++ .../WebSearchInterceptionSettings.test.tsx | 26 +++++++++++++++ .../WebSearchInterceptionSettings.tsx | 15 ++++++++- ui/litellm-dashboard/src/lib/http/schema.d.ts | 6 ++++ 5 files changed, 100 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index a972f08b8bf..7bdadeadf86 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -506,6 +506,16 @@ class WebSearchInterceptionSettings(BaseModel): class WebSearchInterceptionSettingsResponse(SettingsResponse): """Response model for web search interception settings""" + active_on_this_pod: bool = Field( + default=False, + description=( + "Whether the process answering this request has the interception callback " + "registered. Read-only: it reports what is running here, while values.enabled " + "is the cluster-wide setting, and the two disagree while a pod is still " + "applying a change or failed to apply it." + ), + ) + def _with_websearch_enabled_resolved(config: Mapping[str, object]) -> dict[str, object]: """ @@ -1496,11 +1506,22 @@ async def get_websearch_interception_settings( config: Final = await proxy_config.get_config() - return await _get_settings_with_schema( + from litellm.integrations.websearch_interception.handler import ( + WebSearchInterceptionLogger, + ) + + settings: Final = await _get_settings_with_schema( settings_key="websearch_interception_params", settings_class=WebSearchInterceptionSettings, config=_with_websearch_enabled_resolved(config), ) + return WebSearchInterceptionSettingsResponse( + values=settings["values"], + field_schema=settings["field_schema"], + active_on_this_pod=bool( + litellm.logging_callback_manager.get_custom_loggers_for_type(WebSearchInterceptionLogger) + ), + ) @router.patch( diff --git a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py index 9af559e3660..c305f7262da 100644 --- a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py +++ b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py @@ -3263,6 +3263,38 @@ class TestWebSearchInterceptionSettingsEndpoints: assert resp.status_code == 200, resp.text assert resp.json()["values"]["enabled"] is True + def test_get_flags_a_pod_that_has_not_applied_the_stored_setting( + self, mock_proxy_config, mock_auth, monkeypatch + ): + import litellm + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", object()) + monkeypatch.setattr(litellm, "callbacks", []) + mock_proxy_config["config"]["litellm_settings"]["websearch_interception_params"] = {"enabled": True} + + resp = client.get("/get/websearch_interception_settings") + + assert resp.status_code == 200, resp.text + assert resp.json()["values"]["enabled"] is True + assert resp.json()["active_on_this_pod"] is False + + def test_get_reports_the_pod_as_active_once_the_callback_is_registered( + self, mock_proxy_config, mock_auth, monkeypatch + ): + import litellm + from litellm.integrations.websearch_interception.handler import ( + WebSearchInterceptionLogger, + ) + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", object()) + monkeypatch.setattr(litellm, "callbacks", [WebSearchInterceptionLogger(search_tool_name="running")]) + mock_proxy_config["config"]["litellm_settings"]["websearch_interception_params"] = {"enabled": True} + + resp = client.get("/get/websearch_interception_settings") + + assert resp.status_code == 200, resp.text + assert resp.json()["active_on_this_pod"] is True + def test_get_reports_no_database_instead_of_empty_settings(self, mock_proxy_config, mock_auth, monkeypatch): monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.test.tsx index ae981aa9767..a3d09c8f6d6 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.test.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.test.tsx @@ -127,6 +127,32 @@ describe("WebSearchInterceptionSettings", () => { expect(mockMutate.mock.calls[0][0]).toEqual(ENABLED_PAYLOAD); }); + it("warns when the cluster has it on but the serving pod has not applied it", async () => { + vi.mocked(useWebSearchInterceptionSettings).mockReturnValue({ + data: { ...storedSettings, values: { ...storedSettings.values, enabled: true }, active_on_this_pod: false }, + isLoading: false, + isError: false, + error: null, + } as any); + + await renderSettings(); + + expect(screen.getByText(/has not applied it/i)).toBeInTheDocument(); + }); + + it("stays quiet when the serving pod has applied the cluster setting", async () => { + vi.mocked(useWebSearchInterceptionSettings).mockReturnValue({ + data: { ...storedSettings, values: { ...storedSettings.values, enabled: true }, active_on_this_pod: true }, + isLoading: false, + isError: false, + error: null, + } as any); + + await renderSettings(); + + expect(screen.queryByText(/has not applied it/i)).not.toBeInTheDocument(); + }); + it("ignores stored values whose types do not match the field", async () => { vi.mocked(useWebSearchInterceptionSettings).mockReturnValue({ data: { diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.tsx index 3e9e04e720b..93b9cbffd32 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.tsx @@ -5,7 +5,7 @@ import { useUpdateWebSearchInterceptionSettings } from "@/app/(dashboard)/hooks/ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { toast } from "@/lib/toast"; import { Skeleton } from "@/components/ui/skeleton"; -import { CircleHelp, Info, Save } from "lucide-react"; +import { CircleHelp, Info, Save, TriangleAlert } from "lucide-react"; import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -293,9 +293,22 @@ export default function WebSearchInterceptionSettings() { } const values: WebSearchInterceptionStoredValues = toStoredValues(data?.values ?? NO_STORED_VALUES); + const notAppliedHere = values.enabled === true && data?.active_on_this_pod === false; return (
+ {notAppliedHere && ( + + + Not running on the proxy that answered this page + + Interception is switched on for the cluster, but the proxy serving this page has not applied it. That is + expected for about 10 seconds after a change or a restart. If it persists, check that proxy's logs: + requests it handles are not being intercepted. + + + )} + Web Search Interception diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 7e725da3f46..cc08f4d3791 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -41232,6 +41232,12 @@ export interface components { * @description Response model for web search interception settings */ WebSearchInterceptionSettingsResponse: { + /** + * Active On This Pod + * @description Whether the process answering this request has the interception callback registered. Read-only: it reports what is running here, while values.enabled is the cluster-wide setting, and the two disagree while a pod is still applying a change or failed to apply it. + * @default false + */ + active_on_this_pod: boolean; /** Field Schema */ field_schema: { [key: string]: unknown;