mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
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.
This commit is contained in:
parent
4ea21cb75c
commit
c0bc45224c
5 changed files with 100 additions and 2 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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's logs:
|
||||
requests it handles are not being intercepted.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Alert variant="info" className="mb-6">
|
||||
<Info />
|
||||
<AlertTitle>Web Search Interception</AlertTitle>
|
||||
|
|
|
|||
6
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
6
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue