Merge pull request #42042 from BerriAI/litellm_websearch_activation_status

feat(ui): report whether the serving proxy has applied web search interception
This commit is contained in:
yuneng-jiang 2026-09-19 16:42:05 -07:00 committed by GitHub
commit 3df159308d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 100 additions and 2 deletions

View file

@ -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(

View file

@ -3324,6 +3324,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)

View file

@ -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: {

View file

@ -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 (
<div className="w-full">
{notAppliedHere && (
<Alert variant="warning" className="mb-6">
<TriangleAlert />
<AlertTitle>Not running on the proxy that answered this page</AlertTitle>
<AlertDescription>
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&apos;s logs:
requests it handles are not being intercepted.
</AlertDescription>
</Alert>
)}
<Alert variant="info" className="mb-6">
<Info />
<AlertTitle>Web Search Interception</AlertTitle>

View file

@ -41343,6 +41343,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;