mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-03 07:23:33 +00:00
patch bundle fixes
This commit is contained in:
parent
80d96628b4
commit
04bbb549f2
5 changed files with 106 additions and 43 deletions
|
|
@ -2296,14 +2296,12 @@ class UserAPIKeyAuth(
|
|||
# If values is already an instance (not a dict), return it as-is
|
||||
if not isinstance(values, dict):
|
||||
return values
|
||||
if values.get("api_key") is not None:
|
||||
values.update(
|
||||
{"token": cls._safe_hash_litellm_api_key(values.get("api_key"))}
|
||||
)
|
||||
if isinstance(values.get("api_key"), str):
|
||||
values.update(
|
||||
{"api_key": cls._safe_hash_litellm_api_key(values.get("api_key"))}
|
||||
)
|
||||
# api_key can be None (for team_id auth), or non-str from cache/JSON.
|
||||
# Only hash when it's a str to avoid AttributeError in _safe_hash_litellm_api_key.
|
||||
api_key = values.get("api_key")
|
||||
if isinstance(api_key, str):
|
||||
hashed = cls._safe_hash_litellm_api_key(api_key)
|
||||
values.update({"token": hashed, "api_key": hashed})
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Literal, Optional, Union
|
||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Literal, Optional, Sequence, Union
|
||||
|
||||
import litellm
|
||||
from litellm import get_secret
|
||||
|
|
@ -586,6 +586,20 @@ def process_callback(
|
|||
return result
|
||||
|
||||
|
||||
def _dedupe_callbacks_preserve_dict_entries(
|
||||
callbacks: Sequence[Any],
|
||||
) -> List[Union[str, Dict[str, Any]]]:
|
||||
"""Deduplicate callbacks by name. Safe for unhashable dicts."""
|
||||
seen: set = set()
|
||||
result: List[Union[str, Dict[str, Any]]] = []
|
||||
for c in callbacks:
|
||||
name = c if isinstance(c, str) else next(iter(c.keys()))
|
||||
if name not in seen:
|
||||
seen.add(name)
|
||||
result.append(c)
|
||||
return result
|
||||
|
||||
|
||||
def normalize_callback_names(
|
||||
callbacks: Iterable[Union[str, object]] | None,
|
||||
) -> List[Union[str, object]]:
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ from litellm.proxy._types import (
|
|||
UserAPIKeyAuth,
|
||||
)
|
||||
from litellm.proxy.common_utils.callback_utils import (
|
||||
_dedupe_callbacks_preserve_dict_entries,
|
||||
get_callbacks_from_callback_settings,
|
||||
normalize_callback_names,
|
||||
process_callback,
|
||||
|
|
@ -2675,26 +2676,29 @@ class ProxyConfig:
|
|||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
merged_success = list(
|
||||
dict.fromkeys(success_from_settings + litellm_success)
|
||||
merged_success = _dedupe_callbacks_preserve_dict_entries(
|
||||
success_from_settings + litellm_success
|
||||
)
|
||||
for callback in merged_success:
|
||||
callback_name = (
|
||||
callback if isinstance(callback, str) else next(iter(callback.keys()))
|
||||
)
|
||||
# user passed custom_callbacks.async_on_succes_logger. They need us to import a function
|
||||
if "." in callback:
|
||||
if "." in callback_name:
|
||||
litellm.logging_callback_manager.add_litellm_success_callback(
|
||||
get_instance_fn(value=callback)
|
||||
get_instance_fn(value=callback_name)
|
||||
)
|
||||
# these are litellm callbacks - "langfuse", "sentry", "wandb"
|
||||
else:
|
||||
resolved = (
|
||||
litellm.logging_callback_manager._add_custom_callback_generic_api_str(
|
||||
callback
|
||||
callback_name
|
||||
)
|
||||
)
|
||||
litellm.logging_callback_manager.add_litellm_success_callback(
|
||||
resolved
|
||||
)
|
||||
if "prometheus" in callback:
|
||||
if "prometheus" in callback_name:
|
||||
from litellm.integrations.prometheus import (
|
||||
PrometheusLogger,
|
||||
)
|
||||
|
|
@ -2717,20 +2721,23 @@ class ProxyConfig:
|
|||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
merged_failure = list(
|
||||
dict.fromkeys(failure_from_settings + litellm_failure)
|
||||
merged_failure = _dedupe_callbacks_preserve_dict_entries(
|
||||
failure_from_settings + litellm_failure
|
||||
)
|
||||
for callback in merged_failure:
|
||||
callback_name = (
|
||||
callback if isinstance(callback, str) else next(iter(callback.keys()))
|
||||
)
|
||||
# user passed custom_callbacks.async_on_succes_logger. They need us to import a function
|
||||
if "." in callback:
|
||||
if "." in callback_name:
|
||||
litellm.logging_callback_manager.add_litellm_failure_callback(
|
||||
get_instance_fn(value=callback)
|
||||
get_instance_fn(value=callback_name)
|
||||
)
|
||||
# these are litellm callbacks - "langfuse", "sentry", "wandb"
|
||||
else:
|
||||
resolved = (
|
||||
litellm.logging_callback_manager._add_custom_callback_generic_api_str(
|
||||
callback
|
||||
callback_name
|
||||
)
|
||||
)
|
||||
litellm.logging_callback_manager.add_litellm_failure_callback(
|
||||
|
|
@ -3614,11 +3621,11 @@ class ProxyConfig:
|
|||
failure_callbacks = litellm_settings.get("failure_callback", None)
|
||||
callbacks = litellm_settings.get("callbacks", None)
|
||||
|
||||
merged_success = list(
|
||||
dict.fromkeys(success_from_settings + (success_callbacks or []))
|
||||
merged_success = _dedupe_callbacks_preserve_dict_entries(
|
||||
success_from_settings + (success_callbacks or [])
|
||||
)
|
||||
merged_failure = list(
|
||||
dict.fromkeys(failure_from_settings + (failure_callbacks or []))
|
||||
merged_failure = _dedupe_callbacks_preserve_dict_entries(
|
||||
failure_from_settings + (failure_callbacks or [])
|
||||
)
|
||||
seen_both = {c if isinstance(c, str) else next(iter(c)) for c in callbacks_from_settings}
|
||||
merged_callbacks: list = list(callbacks_from_settings)
|
||||
|
|
@ -3629,15 +3636,25 @@ class ProxyConfig:
|
|||
seen_both.add(name)
|
||||
|
||||
for success_callback in merged_success:
|
||||
_name = (
|
||||
success_callback
|
||||
if isinstance(success_callback, str)
|
||||
else next(iter(success_callback.keys()))
|
||||
)
|
||||
self._add_callback_from_db_to_in_memory_litellm_callbacks(
|
||||
callback=success_callback,
|
||||
callback=_name,
|
||||
event_types=["success"],
|
||||
existing_callbacks=litellm.success_callback,
|
||||
)
|
||||
|
||||
for failure_callback in merged_failure:
|
||||
_name = (
|
||||
failure_callback
|
||||
if isinstance(failure_callback, str)
|
||||
else next(iter(failure_callback.keys()))
|
||||
)
|
||||
self._add_callback_from_db_to_in_memory_litellm_callbacks(
|
||||
callback=failure_callback,
|
||||
callback=_name,
|
||||
event_types=["failure"],
|
||||
existing_callbacks=litellm.failure_callback,
|
||||
)
|
||||
|
|
@ -11613,20 +11630,16 @@ async def get_config(): # noqa: PLR0915
|
|||
return [val]
|
||||
return list(val) if val else []
|
||||
|
||||
_success_callbacks = list(
|
||||
dict.fromkeys(
|
||||
_success_from_settings
|
||||
+ normalize_callback_names(
|
||||
_to_list(_litellm_settings.get("success_callback"))
|
||||
)
|
||||
_success_callbacks = _dedupe_callbacks_preserve_dict_entries(
|
||||
_success_from_settings
|
||||
+ normalize_callback_names(
|
||||
_to_list(_litellm_settings.get("success_callback"))
|
||||
)
|
||||
)
|
||||
_failure_callbacks = list(
|
||||
dict.fromkeys(
|
||||
_failure_from_settings
|
||||
+ normalize_callback_names(
|
||||
_to_list(_litellm_settings.get("failure_callback"))
|
||||
)
|
||||
_failure_callbacks = _dedupe_callbacks_preserve_dict_entries(
|
||||
_failure_from_settings
|
||||
+ normalize_callback_names(
|
||||
_to_list(_litellm_settings.get("failure_callback"))
|
||||
)
|
||||
)
|
||||
_litellm_callbacks = _litellm_settings.get("callbacks", []) or []
|
||||
|
|
@ -11655,29 +11668,40 @@ async def get_config(): # noqa: PLR0915
|
|||
"""
|
||||
|
||||
for _callback in _success_callbacks:
|
||||
_name = (
|
||||
_callback if isinstance(_callback, str) else next(iter(_callback.keys()))
|
||||
)
|
||||
_data_to_return.append(
|
||||
process_callback(_callback, "success", environment_variables)
|
||||
process_callback(_name, "success", environment_variables)
|
||||
)
|
||||
|
||||
for _callback in _failure_callbacks:
|
||||
_name = (
|
||||
_callback if isinstance(_callback, str) else next(iter(_callback.keys()))
|
||||
)
|
||||
_data_to_return.append(
|
||||
process_callback(_callback, "failure", environment_variables)
|
||||
process_callback(_name, "failure", environment_variables)
|
||||
)
|
||||
|
||||
for _callback in _success_and_failure_callbacks:
|
||||
_callback_params = None
|
||||
_callback_name: str
|
||||
if isinstance(_callback, dict) and len(_callback) == 1:
|
||||
(_callback_name, _callback_params), = _callback.items()
|
||||
_callback = _callback_name
|
||||
elif isinstance(_callback, str) and _callback in _callback_settings:
|
||||
_callback_name = _callback
|
||||
_raw = _callback_settings.get(_callback) or {}
|
||||
_callback_params = (
|
||||
{k: v for k, v in _raw.items() if k not in ("callback_type", "event_types")}
|
||||
or None
|
||||
)
|
||||
else:
|
||||
_callback_name = (
|
||||
_callback if isinstance(_callback, str) else next(iter(_callback.keys()))
|
||||
)
|
||||
_data_to_return.append(
|
||||
process_callback(
|
||||
_callback,
|
||||
_callback_name,
|
||||
"success_and_failure",
|
||||
environment_variables,
|
||||
callback_params=_callback_params,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ sys.path.insert(
|
|||
) # Adds the parent directory to the system path
|
||||
|
||||
from litellm.proxy.common_utils.callback_utils import (
|
||||
_dedupe_callbacks_preserve_dict_entries,
|
||||
get_callbacks_from_callback_settings,
|
||||
get_model_group_from_request_data,
|
||||
get_remaining_tokens_and_requests_from_request_data,
|
||||
|
|
@ -128,6 +129,27 @@ def test_normalize_callback_names_none_returns_empty_list():
|
|||
assert normalize_callback_names([]) == []
|
||||
|
||||
|
||||
def test_dedupe_callbacks_preserve_dict_entries_handles_dict_style():
|
||||
"""dict.fromkeys would crash on unhashable dicts; _dedupe_callbacks_preserve_dict_entries is safe."""
|
||||
callbacks = [
|
||||
"langfuse",
|
||||
{"websearch_interception": {"enabled_providers": ["bedrock"]}},
|
||||
"langfuse",
|
||||
"sentry",
|
||||
]
|
||||
result = _dedupe_callbacks_preserve_dict_entries(callbacks)
|
||||
assert result == [
|
||||
"langfuse",
|
||||
{"websearch_interception": {"enabled_providers": ["bedrock"]}},
|
||||
"sentry",
|
||||
]
|
||||
|
||||
|
||||
def test_dedupe_callbacks_preserve_dict_entries_strings_only():
|
||||
"""Strings-only input dedupes correctly."""
|
||||
assert _dedupe_callbacks_preserve_dict_entries(["a", "b", "a"]) == ["a", "b"]
|
||||
|
||||
|
||||
def test_remaining_tokens_requests_returns_empty_when_metadata_empty_or_missing():
|
||||
"""Empty or missing metadata returns {}."""
|
||||
assert get_remaining_tokens_and_requests_from_request_data({}) == {}
|
||||
|
|
|
|||
|
|
@ -272,6 +272,7 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
|
|||
...normalized,
|
||||
...paramFields,
|
||||
callback: selectedEditCallback.name,
|
||||
callback_type: selectedEditCallback.type ?? "success",
|
||||
});
|
||||
}
|
||||
}, [showEditCallback, selectedEditCallback, editForm]);
|
||||
|
|
@ -340,7 +341,8 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
|
|||
setIsAddingCallback(true);
|
||||
}
|
||||
|
||||
const payload = buildCallbackPayload(formValues, callbackName, callbacks, isEdit, selectedEditCallback?.type as "success" | "failure" | "success_and_failure" | undefined, callbackSettings);
|
||||
const callbackType = (isEdit ? formValues.callback_type : undefined) ?? selectedEditCallback?.type as "success" | "failure" | "success_and_failure" | undefined;
|
||||
const payload = buildCallbackPayload(formValues, callbackName, callbacks, isEdit, callbackType, callbackSettings);
|
||||
|
||||
try {
|
||||
await setCallbacksCall(accessToken, payload);
|
||||
|
|
@ -782,6 +784,9 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
|
|||
>
|
||||
{selectedEditCallback && (
|
||||
<>
|
||||
<FormItem name="callback_type" hidden>
|
||||
<input type="hidden" />
|
||||
</FormItem>
|
||||
<CallbackSelector
|
||||
callbackConfigs={callbackConfigs}
|
||||
selectedCallback={selectedEditCallback.name}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue