Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/test-mcp-dependency-resolution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ jobs:
uv pip check --python ".venv-$extra"
if [ "$extra" = core ]; then
checker=("$GITHUB_WORKSPACE/tests/base_sdk_tests/check_base_sdk_install.py")
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/.venv-$extra/bin/python" -I "$GITHUB_WORKSPACE/tests/base_sdk_tests/check_sdk_http.py")
else
checker=("$GITHUB_WORKSPACE/scripts/check_mcp_sdk_install.py" --extra "$extra")
fi
Expand Down Expand Up @@ -162,10 +163,14 @@ jobs:
uv venv --python ${{ matrix.python-version }} ".venv-core-$resolution"
uv pip install --python ".venv-core-$resolution" --resolution "$resolution" "$wheel"
uv pip check --python ".venv-core-$resolution"
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/.venv-core-$resolution/bin/python" -I "$GITHUB_WORKSPACE/tests/base_sdk_tests/check_base_sdk_install.py")
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/.venv-core-$resolution/bin/python" -I "$GITHUB_WORKSPACE/tests/base_sdk_tests/check_base_sdk_install.py" --profile core)
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/.venv-core-$resolution/bin/python" -I "$GITHUB_WORKSPACE/tests/base_sdk_tests/check_sdk_http.py")
done
uv pip install --python .venv-core-highest coverage==7.14.0
.venv-core-highest/bin/python -I -m coverage run --rcfile=/dev/null --append --branch --include="*/tests/base_sdk_tests/check_base_sdk_install.py" tests/base_sdk_tests/check_base_sdk_install.py
.venv-core-highest/bin/python -I -m coverage run --rcfile=/dev/null --append --branch --include="*/tests/base_sdk_tests/check_base_sdk_install.py" tests/base_sdk_tests/check_base_sdk_install.py --profile core
uv pip install --python .venv-core-highest boto3 tokenizers huggingface-hub
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/.venv-core-highest/bin/python" -I "$GITHUB_WORKSPACE/tests/base_sdk_tests/check_base_sdk_install.py" --profile dependencies)
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/.venv-core-highest/bin/python" -I "$GITHUB_WORKSPACE/tests/base_sdk_tests/check_sdk_http.py")
uv run --no-project --with coverage==7.14.0 python -m coverage xml --rcfile=/dev/null -o coverage-core.xml
- name: Upload core packaging coverage
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
Expand Down
3 changes: 2 additions & 1 deletion litellm/integrations/focus/destinations/s3_destination.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from datetime import timezone
from typing import Final, TypedDict

import boto3
from typing_extensions import ReadOnly

from .base import FocusDestination, FocusTimeWindow
Expand Down Expand Up @@ -75,6 +74,8 @@ def _client_kwargs(self) -> _S3ClientKwargs:
}

def _upload(self, content: bytes, object_key: str) -> None:
import boto3

s3_client: Final = boto3.client("s3", **self._client_kwargs())
s3_client.put_object(
Bucket=self.bucket_name,
Expand Down
8 changes: 6 additions & 2 deletions litellm/litellm_core_utils/exception_mapping_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2376,6 +2376,10 @@ def exception_type(
return original_exception
if _is_guardrail_block(original_exception):
return original_exception
if isinstance(original_exception, ImportError) and (
original_exception.name in ("boto3", "botocore") or custom_llm_provider in ("bedrock", "bedrock_mantle")
Comment thread
greptile-apps[bot] marked this conversation as resolved.
):
return original_exception
exception_mapping_worked = False
exception_provider = custom_llm_provider
mappable_exception: Final[_ProviderHTTPException] = cast("_ProviderHTTPException", original_exception)
Expand All @@ -2398,9 +2402,9 @@ def exception_type(
if model or custom_llm_provider:
if hasattr(original_exception, "message"):
error_str = (
redact_secret_string(str(original_exception.message))
redact_secret_string(str(mappable_exception.message))
if _ENABLE_SECRET_REDACTION
else str(original_exception.message)
else str(mappable_exception.message)
)
if isinstance(original_exception, BaseException):
exception_type = type(original_exception).__name__
Expand Down
13 changes: 13 additions & 0 deletions litellm/litellm_core_utils/optional_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import Final


def ensure_optional_import(module: str) -> None:
try:
__import__(module)
except ModuleNotFoundError as error:
if error.name != module:
raise
package: Final = "boto3" if module == "botocore" else module
raise ModuleNotFoundError(
f"Missing optional dependency '{module}'. Run 'pip install {package}'.", name=module
) from error
11 changes: 7 additions & 4 deletions litellm/litellm_core_utils/token_counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import struct
from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
from itertools import accumulate
from typing import Final, Literal, cast
from typing import TYPE_CHECKING, Final, Literal, cast

import anyio
import anyio.lowlevel
Expand All @@ -30,7 +30,10 @@
TOKEN_COUNTER_MAX_EXACT_CHARS,
)
from litellm.litellm_core_utils.asyncify import asyncify
from litellm.litellm_core_utils.tokenizer import Encoding, HuggingFace, HuggingFaceTokenizer, OpenAIEncoding
from litellm.litellm_core_utils.tokenizer import HuggingFaceTokenizer, OpenAIEncoding

if TYPE_CHECKING:
from litellm.litellm_core_utils.tokenizer import Encoding, HuggingFace
Comment on lines +35 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Tokenizer types cannot be inspected

Moving Encoding behind TYPE_CHECKING makes get_type_hints(openai_tokenizer_encoding) raise NameError instead of returning its types. The same problem affects tokenizer functions in litellm/rust_bridge/tokenizer.py. Keep the annotation names available at runtime without eagerly loading optional packages

from litellm.litellm_core_utils.url_utils import safe_get
from litellm.llms.custom_httpx.http_handler import get_httpx_client
from litellm.rust_bridge.tokenizer import get_encoding
Expand Down Expand Up @@ -680,13 +683,13 @@ def encode_length(text: str) -> int:
raise ValueError("Unsupported tokenizer type")


def _encoding_count(encoding: Encoding, text: str) -> int:
def _encoding_count(encoding: "Encoding", text: str) -> int:
if isinstance(encoding, OpenAIEncoding):
return encoding.count(text)
return len(encoding.encode(text, disallowed_special=()))


def openai_tokenizer_encoding(model: str) -> Encoding:
def openai_tokenizer_encoding(model: str) -> "Encoding":
"""The encoding `token_counter` uses for a model on the `openai_tokenizer` path."""
return get_encoding(openai_tokenizer_encoding_name(model))

Expand Down
60 changes: 54 additions & 6 deletions litellm/litellm_core_utils/tokenizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@
from dataclasses import dataclass
from functools import partial
from pathlib import Path
from types import MappingProxyType
from types import MappingProxyType, UnionType
from typing import TYPE_CHECKING, Final, Literal, Protocol, TypeAlias, runtime_checkable

import tiktoken
from tokenizers import AddedToken
from tokenizers import Tokenizer as PythonHuggingFaceTokenizer

if TYPE_CHECKING:
import numpy as np
Comment thread
greptile-apps[bot] marked this conversation as resolved.
import numpy.typing as npt
from tokenizers import Tokenizer as PythonHuggingFaceTokenizer

from litellm.rust_bridge._native import HuggingFaceEncoding
from litellm.rust_bridge._native import Tokenizer as NativeTokenizer
Expand Down Expand Up @@ -217,6 +216,19 @@ def _allowed(self, text: str, allowed_special: AllowedSpecial, disallowed_specia
return allowed


@dataclass(frozen=True, slots=True)
class _NativeAddedToken:
content: str
single_word: bool
lstrip: bool
rstrip: bool
normalized: bool
special: bool

def __str__(self) -> str:
return self.content


@dataclass(frozen=True, slots=True)
class HuggingFaceTokenizer:
"""The read-only ``tokenizers.Tokenizer`` surface over the Rust Hugging Face codec."""
Expand Down Expand Up @@ -269,7 +281,14 @@ def get_vocab(self, with_added_tokens: bool = True) -> dict[str, int]: # mutabl
def get_vocab_size(self, with_added_tokens: bool = True) -> int:
return self._native.get_vocab_size(with_added_tokens)

def get_added_tokens_decoder(self) -> dict[int, AddedToken]: # mutable-ok: [LIT001] SDK return type
def get_added_tokens_decoder(self) -> dict[int, _AddedToken]: # mutable-ok: [LIT001] SDK return type
try:
from tokenizers import AddedToken
except ModuleNotFoundError as error:
if error.name != "tokenizers":
raise
return {token_id: _NativeAddedToken(*data) for token_id, data in self._native.added_tokens_decoder()}

Comment thread
cursor[bot] marked this conversation as resolved.
return {
token_id: AddedToken(
content, single_word=single_word, lstrip=lstrip, rstrip=rstrip, normalized=normalized, special=special
Expand Down Expand Up @@ -361,11 +380,40 @@ def _batch_input(


Encoding: TypeAlias = tiktoken.Encoding | OpenAIEncoding
HuggingFace: TypeAlias = PythonHuggingFaceTokenizer | HuggingFaceTokenizer
Tokenizer: TypeAlias = Encoding | HuggingFace
if TYPE_CHECKING:
HuggingFace: TypeAlias = PythonHuggingFaceTokenizer | HuggingFaceTokenizer
Tokenizer: TypeAlias = Encoding | HuggingFace


def __getattr__(name: str) -> UnionType | type[HuggingFaceTokenizer]:
if name not in {"HuggingFace", "Tokenizer"}:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
available: Final = HuggingFaceTokenizer if name == "HuggingFace" else Encoding | HuggingFaceTokenizer
try:
from tokenizers import Tokenizer as PythonTokenizer
except ModuleNotFoundError as error:
if error.name == "tokenizers":
return available
raise
return available | PythonTokenizer


class _AddedToken(Protocol):
@property
def content(self) -> str: ...

@property
def single_word(self) -> bool: ...

@property
def lstrip(self) -> bool: ...

@property
def rstrip(self) -> bool: ...

@property
def normalized(self) -> bool: ...

@property
def special(self) -> bool: ...

Expand Down
9 changes: 4 additions & 5 deletions litellm/llms/aws_polly/text_to_speech/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import httpx

from litellm.litellm_core_utils.aws_partition import get_aws_dns_suffix
from litellm.litellm_core_utils.optional_imports import ensure_optional_import
from litellm.llms.base_llm.text_to_speech.transformation import (
BaseTextToSpeechConfig,
TextToSpeechRequestData,
Expand Down Expand Up @@ -262,11 +263,9 @@ def _sign_polly_request(
Returns:
Tuple of (signed_headers, json_body_string)
"""
try:
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
except ImportError:
raise ImportError("Missing boto3 to call AWS Polly. Run 'pip install boto3'.")
ensure_optional_import("botocore")
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest

# Get AWS region
aws_region_name: Final = litellm_params.get("aws_region_name", self.DEFAULT_REGION)
Expand Down
Loading
Loading