mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
refactor(rust-bridge): warn about client= once, in function_setup
function_setup runs once per top-level SDK call on both paths: @client calls it for Python and the legacy logging adapter calls it for Rust. Warning there replaces the warnings in PublicDispatch and transcription, and native_call_hook no longer strips client, since Rust never reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
312c5f8988
commit
3d1f361904
8 changed files with 35 additions and 82 deletions
|
|
@ -158,7 +158,6 @@ from litellm.llms.vertex_ai.common_utils import (
|
|||
)
|
||||
from litellm.realtime_api.main import realtime_health_check
|
||||
from litellm.responses.mcp.tool_precheck import may_name_litellm_mcp_gateway
|
||||
from litellm.rust_bridge.ignored_arguments import warn_deprecated, warn_deprecated_in
|
||||
from litellm.secret_managers.main import get_secret_bool, get_secret_str
|
||||
from litellm.text_completions.streaming_iterator import TextCompletionStreamWrapper
|
||||
from litellm.types.completion import (
|
||||
|
|
@ -8413,7 +8412,6 @@ async def atranscription(
|
|||
loop: Final = asyncio.get_event_loop()
|
||||
model: Final = args[0] if len(args) > 0 else kwargs["model"]
|
||||
### PASS ARGS TO Image Generation ###
|
||||
warn_deprecated_in(_REQUEST_KWARGS_ADAPTER.validate_python(kwargs))
|
||||
kwargs["atranscription"] = True
|
||||
file: Final = kwargs.get("file", None)
|
||||
try:
|
||||
|
|
@ -8568,8 +8566,6 @@ def transcription(
|
|||
client: openai.AsyncOpenAI | openai.OpenAI | openai.AzureOpenAI | openai.AsyncAzureOpenAI | None = kwargs.pop(
|
||||
"client", None
|
||||
)
|
||||
if client is not None and not atranscription:
|
||||
warn_deprecated("client")
|
||||
|
||||
if litellm_logging_obj:
|
||||
litellm_logging_obj.model_call_details["client"] = str(client)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ _DEPRECATED: Final = MappingProxyType(
|
|||
"client": "client= is deprecated: litellm is moving requests to Rust, which ignores it and uses its own HTTP settings"
|
||||
}
|
||||
)
|
||||
_INTERNAL: Final = frozenset({"shared_session"})
|
||||
_DROPPED: Final = _INTERNAL | frozenset(_DEPRECATED)
|
||||
|
||||
|
||||
def _first_stacklevel_outside_litellm() -> int:
|
||||
|
|
@ -40,9 +38,3 @@ def warn_deprecated(name: str) -> None:
|
|||
def warn_deprecated_in(kwargs: Mapping[str, object]) -> None:
|
||||
for name in (name for name in _DEPRECATED if kwargs.get(name) is not None):
|
||||
warn_deprecated(name)
|
||||
|
||||
|
||||
def without_ignored(kwargs: Mapping[str, object]) -> Mapping[str, object]:
|
||||
if _DROPPED.isdisjoint(kwargs):
|
||||
return kwargs
|
||||
return MappingProxyType({key: value for key, value in kwargs.items() if key not in _DROPPED})
|
||||
|
|
@ -8,7 +8,6 @@ from litellm._logging import verbose_logger
|
|||
from litellm.rust_bridge import catalog, runtime
|
||||
from litellm.rust_bridge.bindings import NativeBinding
|
||||
from litellm.rust_bridge.catalog import Route, RouteContext, Rules
|
||||
from litellm.rust_bridge.ignored_arguments import warn_deprecated_in
|
||||
|
||||
RequestT: Final = TypeVar("RequestT")
|
||||
NativeT: Final = TypeVar("NativeT")
|
||||
|
|
@ -59,7 +58,6 @@ class PublicDispatch(Generic[RequestT]):
|
|||
native: Callable[[NativeT, RequestT, tuple[object, ...], Mapping[str, object]], ResultT],
|
||||
rules: Rules | None = None,
|
||||
) -> ResultT:
|
||||
warn_deprecated_in(kwargs)
|
||||
selected_rules: Final = catalog.RULES if rules is None else rules
|
||||
if isinstance(python, runtime.NoPythonImplementation):
|
||||
native_request: Final = self._native_request(args, kwargs)
|
||||
|
|
@ -96,7 +94,6 @@ class PublicDispatch(Generic[RequestT]):
|
|||
native: Callable[[NativeT, RequestT, tuple[object, ...], Mapping[str, object]], Awaitable[ResultT]],
|
||||
rules: Rules | None = None,
|
||||
) -> ResultT:
|
||||
warn_deprecated_in(kwargs)
|
||||
selected_rules: Final = catalog.RULES if rules is None else rules
|
||||
if isinstance(python, runtime.NoPythonImplementation):
|
||||
native_request: Final = self._native_request(args, kwargs)
|
||||
|
|
|
|||
|
|
@ -4,12 +4,10 @@ from __future__ import annotations
|
|||
|
||||
import inspect
|
||||
from collections.abc import Callable, Collection, Mapping, Sequence
|
||||
from dataclasses import dataclass, replace
|
||||
from dataclasses import dataclass
|
||||
from types import MappingProxyType
|
||||
from typing import Final, TypeVar, cast # noqa: TID251 # narrows caller-owned containers without copying them
|
||||
|
||||
from litellm.rust_bridge.ignored_arguments import without_ignored
|
||||
|
||||
|
||||
def signature(legacy: Callable[..., object]) -> inspect.Signature:
|
||||
return inspect.signature(legacy)
|
||||
|
|
@ -116,4 +114,4 @@ def native_call_hook(
|
|||
_args: tuple[object, ...],
|
||||
_kwargs: Mapping[str, object],
|
||||
) -> NativeResultT:
|
||||
return hook(replace(call, kwargs=without_ignored(call.kwargs)))
|
||||
return hook(call)
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ from litellm.litellm_core_utils.tool_fields import (
|
|||
from litellm.rust_bridge import tokenizer as tokenizer_dispatch
|
||||
from litellm.rust_bridge.catalog import decision
|
||||
from litellm.rust_bridge.configuration import Decision
|
||||
from litellm.rust_bridge.deprecated_arguments import warn_deprecated_in
|
||||
|
||||
_CachingHandlerResponse = None
|
||||
_LLMCachingHandler = None
|
||||
|
|
@ -1267,6 +1268,7 @@ def function_setup(
|
|||
**kwargs: Any, # kwargs-ok: forwarded to Logging()/callbacks, varies per call_type
|
||||
) -> tuple[LiteLLMLoggingObject, dict[str, Any]]:
|
||||
### NOTICES ###
|
||||
warn_deprecated_in(kwargs)
|
||||
if litellm.set_verbose is True:
|
||||
verbose_logger.warning(
|
||||
"`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs."
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import warnings
|
||||
from collections.abc import AsyncGenerator, Awaitable, Callable, Iterator, Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Final, NoReturn, TypeAlias
|
||||
|
|
@ -330,41 +329,3 @@ async def test_dispatch_without_python_never_falls_back(
|
|||
|
||||
with pytest.raises(NoPythonImplementationError, match=f"ocr has no Python implementation, so .*{reason}"):
|
||||
await dispatch_without_python(ocr_dispatch(request_value), bound, rules, asynchronous=asynchronous)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("asynchronous", [False, True])
|
||||
async def test_python_served_call_keeps_the_client_but_warns_it_is_deprecated(asynchronous: bool) -> None:
|
||||
client: Final = object()
|
||||
received: Final[list[object]] = []
|
||||
|
||||
def python(*args: object, **kwargs: object) -> object: # kwargs-ok: records the forwarded call
|
||||
received.append(kwargs["client"])
|
||||
return "python"
|
||||
|
||||
async def apython(*args: object, **kwargs: object) -> object: # kwargs-ok: records the forwarded call
|
||||
return python(*args, **kwargs)
|
||||
|
||||
dispatch: Final = PublicDispatch(
|
||||
route=Route.CHAT_COMPLETIONS,
|
||||
request=lambda args, kwargs: None,
|
||||
context=lambda _: RouteContext(Route.CHAT_COMPLETIONS),
|
||||
)
|
||||
rules: Final[Rules] = (RouteRule(Route.CHAT_COMPLETIONS, Rollout.PYTHON_ONLY),)
|
||||
with warnings.catch_warnings(record=True) as caught:
|
||||
warnings.simplefilter("always")
|
||||
result: Final = (
|
||||
await dispatch.arun(
|
||||
("model",), {"client": client}, python=apython, binding=binding(), native=pytest.fail, rules=rules
|
||||
)
|
||||
if asynchronous
|
||||
else dispatch.run(
|
||||
("model",), {"client": client}, python=python, binding=binding(), native=pytest.fail, rules=rules
|
||||
)
|
||||
)
|
||||
|
||||
assert result == "python"
|
||||
assert received == [client]
|
||||
assert [(warning.category, warning.filename) for warning in caught if "client=" in str(warning.message)] == [
|
||||
(DeprecationWarning, __file__)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import warnings
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.rust_bridge.public_call import AsyncFlag, native_call, native_call_hook, signature
|
||||
from litellm.rust_bridge.public_call import AsyncFlag, native_call, signature
|
||||
|
||||
|
||||
def _messages(
|
||||
|
|
@ -77,25 +76,3 @@ def test_flag_keywords_name_positionals_and_drop_only_the_flag() -> None:
|
|||
def test_flag_keywords_reject_a_parameter_given_twice() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
_FLAG.keywords((128,), {"max_tokens": 64})
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"supplied",
|
||||
(
|
||||
{"client": object(), "api_key": "k"},
|
||||
{"shared_session": object(), "api_key": "k"},
|
||||
{"client": None, "api_key": "k"},
|
||||
{"api_key": "k"},
|
||||
),
|
||||
)
|
||||
def test_hook_hands_rust_the_call_without_client_or_shared_session(supplied: Mapping[str, object]) -> None:
|
||||
extra_body: Final = {"pages": [0]}
|
||||
call: Final = native_call(_SIGNATURE, (128, [], "model"), {**supplied, "extra_body": extra_body})
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
seen: Final = native_call_hook(lambda hooked: hooked.kwargs, call, (), {})
|
||||
|
||||
assert seen == {"api_key": "k", "extra_body": extra_body}
|
||||
assert seen["extra_body"] is extra_body
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import queue
|
|||
import threading
|
||||
import time
|
||||
import uuid
|
||||
import warnings
|
||||
from collections.abc import Callable, Iterator, Mapping
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
|
@ -11624,3 +11625,32 @@ async def test_success_enqueue_is_tracked_without_starting_callbacks_synchronous
|
|||
assert GLOBAL_LOGGING_WORKER.pending_count > 0, "Tracked enqueue must finish before worker draining starts"
|
||||
await GLOBAL_LOGGING_WORKER.flush()
|
||||
assert logging_obj.model_call_details["has_logged_async_success"] is True
|
||||
|
||||
|
||||
def _client_deprecations(caught: list[warnings.WarningMessage]) -> list[tuple[type[Warning], str]]:
|
||||
return [(warning.category, warning.filename) for warning in caught if "client=" in str(warning.message)]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("asynchronous", [False, True])
|
||||
async def test_python_served_call_keeps_the_client_but_warns_once_that_it_is_deprecated(asynchronous: bool) -> None:
|
||||
arguments: Final = {
|
||||
"model": "gpt-4o-mini",
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"mock_response": "hi",
|
||||
"client": object(),
|
||||
}
|
||||
with warnings.catch_warnings(record=True) as caught:
|
||||
warnings.simplefilter("always")
|
||||
response: Final = await litellm.acompletion(**arguments) if asynchronous else litellm.completion(**arguments)
|
||||
|
||||
assert response.choices[0].message.content == "hi"
|
||||
assert _client_deprecations(caught) == [(DeprecationWarning, __file__)]
|
||||
|
||||
|
||||
def test_call_without_a_client_does_not_warn() -> None:
|
||||
with warnings.catch_warnings(record=True) as caught:
|
||||
warnings.simplefilter("always")
|
||||
litellm.completion(model="gpt-4o-mini", messages=[{"role": "user", "content": "hi"}], mock_response="hi")
|
||||
|
||||
assert _client_deprecations(caught) == []
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue