mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
fix(messages): hand rust only the anthropic metadata user_id (#51355)
A guardrail such as Pillar aliases the request's metadata to litellm_metadata, which holds the caller's UserAPIKeyAuth. Since #51119 the Rust host converts the whole metadata field to JSON before its own user_id filter runs, so the call failed with "unsupported type UserAPIKeyAuth". The host now passes only user_id, the one key the Rust validator and Python's AnthropicMetadata keep.
This commit is contained in:
parent
27f54ec379
commit
7553c4f501
4 changed files with 50 additions and 1 deletions
|
|
@ -141,6 +141,13 @@ def without(
|
|||
return {key: value for key, value in mapping.items() if key not in keys}
|
||||
|
||||
|
||||
def only(
|
||||
mapping: Mapping[_Key, _Value], *keys: object
|
||||
) -> dict[_Key, _Value]: # mutable-ok: a fresh copy the caller owns
|
||||
"""``mapping`` with only ``keys``, as a new dict in the same order; ``mapping`` is unchanged."""
|
||||
return {key: value for key, value in mapping.items() if key in keys}
|
||||
|
||||
|
||||
def decoded_json_object(value: object) -> Mapping[str, object]:
|
||||
mapping: Final = as_str_mapping(value)
|
||||
if mapping is not None:
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from litellm.litellm_core_utils.core_helpers import (
|
|||
as_str_mapping,
|
||||
drops_unsupported_params,
|
||||
normalize_drop_params,
|
||||
only,
|
||||
proxy_request_headers,
|
||||
stream_cost_injection_requested,
|
||||
)
|
||||
|
|
@ -174,8 +175,17 @@ def modify_max_tokens(
|
|||
cache.remember_unclamped(kwargs.get("litellm_logging_obj"), unclamped)
|
||||
|
||||
|
||||
def _anthropic_metadata(params: Mapping[str, object]) -> Mapping[str, object]:
|
||||
metadata: Final = as_str_mapping(params.get("metadata"))
|
||||
if metadata is None:
|
||||
return params
|
||||
return {**params, "metadata": only(metadata, "user_id")}
|
||||
|
||||
|
||||
def _body_fields(request: Mapping[str, object]) -> Mapping[str, object]:
|
||||
params: Final = known_params(_BODY_FIELDS.difference(handler.additional_drop_paths(request)), request)
|
||||
params: Final = _anthropic_metadata(
|
||||
known_params(_BODY_FIELDS.difference(handler.additional_drop_paths(request)), request)
|
||||
)
|
||||
if deployment_reasoning_effort_levels(request.get("model_info")) is None:
|
||||
return params
|
||||
return _with_deployment_effort(params, request)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from litellm.litellm_core_utils.core_helpers import (
|
|||
json_contains,
|
||||
map_finish_reason,
|
||||
normalize_drop_params,
|
||||
only,
|
||||
preserve_upstream_non_openai_attributes,
|
||||
process_response_headers,
|
||||
provider_reported_usage_cost,
|
||||
|
|
@ -934,3 +935,14 @@ def test_without_returns_a_new_dict_and_leaves_the_source_unchanged() -> None:
|
|||
|
||||
assert source == {"a": 1, "b": 2}
|
||||
assert result == {"b": 2, "c": 3}
|
||||
|
||||
|
||||
def test_only_keeps_the_named_keys_in_order_and_leaves_the_source_unchanged() -> None:
|
||||
source: Final = {"a": 1, "b": 2, "c": 3, "d": 4}
|
||||
|
||||
result: Final = only(source, "d", "b", "missing")
|
||||
result["e"] = 5
|
||||
|
||||
assert list(result.items()) == [("b", 2), ("d", 4), ("e", 5)]
|
||||
assert only(source) == {}
|
||||
assert source == {"a": 1, "b": 2, "c": 3, "d": 4}
|
||||
|
|
|
|||
|
|
@ -296,6 +296,26 @@ def test_body_params_forward_the_thread_like_the_python_handler(
|
|||
assert messages_host.body_params(request, asynchronous=True).get("thread") == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("metadata", "expected"),
|
||||
(
|
||||
({"user_id": "u-1", "user_api_key_auth": object(), "pillar_flagged": False}, {"user_id": "u-1"}),
|
||||
({"user_api_key_auth": object()}, {}),
|
||||
(["u-1"], ["u-1"]),
|
||||
),
|
||||
ids=("litellm-keys-dropped", "only-litellm-keys", "not-an-object-left-for-rust-to-reject"),
|
||||
)
|
||||
def test_body_params_forward_only_the_metadata_anthropic_accepts(metadata: object, expected: object) -> None:
|
||||
request: Final[dict[str, object]] = {
|
||||
"model": "claude-sonnet-4-5",
|
||||
"messages": [],
|
||||
"max_tokens": 8,
|
||||
"metadata": metadata,
|
||||
}
|
||||
|
||||
assert messages_host.body_params(request, asynchronous=True)["metadata"] == expected
|
||||
|
||||
|
||||
def _marked(phase: str | None = None, *args: object) -> ValueError:
|
||||
error: Final = ValueError(*args)
|
||||
if phase is not None:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue