diff --git a/codecov.yaml b/codecov.yaml index 3baea13e2d3..f5acdd39136 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -3,6 +3,9 @@ codecov: notify: wait_for_ci: false # post as soon as expected uploads arrive, don't wait on CI +ignore: + - "litellm-rust/**" + # Uploads are flagged per workflow/shard (GHA) or "circleci". carryforward makes # a re-upload of a flag replace its prior session instead of accumulating a # conflicting one, and lets a commit reuse a flag from its parent when that flag diff --git a/litellm/llms/azure_ai/ocr/document_intelligence/transformation.py b/litellm/llms/azure_ai/ocr/document_intelligence/transformation.py index d1168378705..cc65ad706ab 100644 --- a/litellm/llms/azure_ai/ocr/document_intelligence/transformation.py +++ b/litellm/llms/azure_ai/ocr/document_intelligence/transformation.py @@ -11,7 +11,7 @@ The operation location must be polled until the analysis completes. import asyncio import re import time -from typing import Any, Dict, Optional +from typing import Any, Dict from urllib.parse import quote import httpx @@ -56,7 +56,7 @@ class AzureDocumentIntelligenceOCRConfig(BaseOCRConfig): def __init__(self) -> None: super().__init__() - def get_api_key_env_var(self) -> Optional[str]: + def get_api_key_env_var(self) -> str | None: return AZURE_DOCUMENT_INTELLIGENCE_API_KEY_ENV_VAR def get_supported_ocr_params(self, model: str) -> list: @@ -149,9 +149,9 @@ class AzureDocumentIntelligenceOCRConfig(BaseOCRConfig): self, headers: Dict, model: str, - api_key: Optional[str] = None, - api_base: Optional[str] = None, - litellm_params: Optional[dict] = None, + api_key: str | None = None, + api_base: str | None = None, + litellm_params: dict | None = None, **kwargs, ) -> Dict: """ @@ -187,10 +187,10 @@ class AzureDocumentIntelligenceOCRConfig(BaseOCRConfig): def get_complete_url( self, - api_base: Optional[str], + api_base: str | None, model: str, optional_params: dict, - litellm_params: Optional[dict] = None, + litellm_params: dict | None = None, **kwargs, ) -> str: """ diff --git a/litellm/llms/azure_ai/ocr/transformation.py b/litellm/llms/azure_ai/ocr/transformation.py index 5708fa6b55e..ee35fc28994 100644 --- a/litellm/llms/azure_ai/ocr/transformation.py +++ b/litellm/llms/azure_ai/ocr/transformation.py @@ -2,7 +2,7 @@ Azure AI OCR transformation implementation. """ -from typing import Dict, Optional +from typing import Dict from litellm._logging import verbose_logger from litellm.litellm_core_utils.prompt_templates.image_handling import ( @@ -32,16 +32,16 @@ class AzureAIOCRConfig(MistralOCRConfig): def __init__(self) -> None: super().__init__() - def get_api_key_env_var(self) -> Optional[str]: + def get_api_key_env_var(self) -> str | None: return AZURE_AI_OCR_API_KEY_ENV_VAR def validate_environment( self, headers: Dict, model: str, - api_key: Optional[str] = None, - api_base: Optional[str] = None, - litellm_params: Optional[dict] = None, + api_key: str | None = None, + api_base: str | None = None, + litellm_params: dict | None = None, **kwargs, ) -> Dict: """ @@ -77,10 +77,10 @@ class AzureAIOCRConfig(MistralOCRConfig): def get_complete_url( self, - api_base: Optional[str], + api_base: str | None, model: str, optional_params: dict, - litellm_params: Optional[dict] = None, + litellm_params: dict | None = None, **kwargs, ) -> str: """ diff --git a/litellm/llms/base_llm/ocr/transformation.py b/litellm/llms/base_llm/ocr/transformation.py index de6bc2471ec..a2946c62506 100644 --- a/litellm/llms/base_llm/ocr/transformation.py +++ b/litellm/llms/base_llm/ocr/transformation.py @@ -2,7 +2,7 @@ Base OCR transformation configuration. """ -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Union import httpx from pydantic import PrivateAttr @@ -25,16 +25,16 @@ DocumentType = Dict[str, str] class OCRPageDimensions(LiteLLMPydanticObjectBase): """Page dimensions from OCR response.""" - dpi: Optional[int] = None - height: Optional[int] = None - width: Optional[int] = None + dpi: int | None = None + height: int | None = None + width: int | None = None class OCRPageImage(LiteLLMPydanticObjectBase): """Image extracted from OCR page.""" - image_base64: Optional[str] = None - bbox: Optional[Dict[str, Any]] = None + image_base64: str | None = None + bbox: Dict[str, Any] | None = None model_config = {"extra": "allow"} @@ -44,8 +44,8 @@ class OCRPage(LiteLLMPydanticObjectBase): index: int markdown: str - images: Optional[List[OCRPageImage]] = None - dimensions: Optional[OCRPageDimensions] = None + images: List[OCRPageImage] | None = None + dimensions: OCRPageDimensions | None = None model_config = {"extra": "allow"} @@ -53,9 +53,9 @@ class OCRPage(LiteLLMPydanticObjectBase): class OCRUsageInfo(LiteLLMPydanticObjectBase): """Usage information from OCR response.""" - pages_processed: Optional[int] = None - credits: Optional[float] = None - doc_size_bytes: Optional[int] = None + pages_processed: int | None = None + credits: float | None = None + doc_size_bytes: int | None = None model_config = {"extra": "allow"} @@ -68,8 +68,8 @@ class OCRResponse(LiteLLMPydanticObjectBase): pages: List[OCRPage] model: str - document_annotation: Optional[Any] = None - usage_info: Optional[OCRUsageInfo] = None + document_annotation: Any | None = None + usage_info: OCRUsageInfo | None = None object: str = "ocr" model_config = {"extra": "allow"} @@ -81,8 +81,8 @@ class OCRResponse(LiteLLMPydanticObjectBase): class OCRRequestData(LiteLLMPydanticObjectBase): """OCR request data structure.""" - data: Optional[Union[Dict, bytes]] = None - files: Optional[Dict[str, Any]] = None + data: Union[Dict, bytes] | None = None + files: Dict[str, Any] | None = None class BaseOCRConfig: @@ -101,7 +101,7 @@ class BaseOCRConfig: """ return [] - def get_api_key_env_var(self) -> Optional[str]: + def get_api_key_env_var(self) -> str | None: """ Return the provider-specific API key environment variable name, if any. """ @@ -120,9 +120,9 @@ class BaseOCRConfig: self, headers: Dict, model: str, - api_key: Optional[str] = None, - api_base: Optional[str] = None, - litellm_params: Optional[dict] = None, + api_key: str | None = None, + api_base: str | None = None, + litellm_params: dict | None = None, **kwargs, ) -> Dict: """ @@ -133,10 +133,10 @@ class BaseOCRConfig: def get_complete_url( self, - api_base: Optional[str], + api_base: str | None, model: str, optional_params: dict, - litellm_params: Optional[dict] = None, + litellm_params: dict | None = None, **kwargs, ) -> str: """ diff --git a/litellm/llms/mistral/ocr/transformation.py b/litellm/llms/mistral/ocr/transformation.py index 49555baaf70..3c0460cd51e 100644 --- a/litellm/llms/mistral/ocr/transformation.py +++ b/litellm/llms/mistral/ocr/transformation.py @@ -2,7 +2,7 @@ Mistral OCR transformation implementation. """ -from typing import Any, Dict, Optional +from typing import Any, Dict import httpx @@ -61,7 +61,7 @@ class MistralOCRConfig(BaseOCRConfig): "id", ] - def get_api_key_env_var(self) -> Optional[str]: + def get_api_key_env_var(self) -> str | None: return MISTRAL_OCR_API_KEY_ENV_VAR def map_ocr_params( @@ -90,9 +90,9 @@ class MistralOCRConfig(BaseOCRConfig): self, headers: Dict, model: str, - api_key: Optional[str] = None, - api_base: Optional[str] = None, - litellm_params: Optional[dict] = None, + api_key: str | None = None, + api_base: str | None = None, + litellm_params: dict | None = None, **kwargs, ) -> Dict: """ @@ -118,10 +118,10 @@ class MistralOCRConfig(BaseOCRConfig): def get_complete_url( self, - api_base: Optional[str], + api_base: str | None, model: str, optional_params: dict, - litellm_params: Optional[dict] = None, + litellm_params: dict | None = None, **kwargs, ) -> str: """ diff --git a/litellm/llms/vertex_ai/ocr/deepseek_transformation.py b/litellm/llms/vertex_ai/ocr/deepseek_transformation.py index bcbdb407df4..a98311d04eb 100644 --- a/litellm/llms/vertex_ai/ocr/deepseek_transformation.py +++ b/litellm/llms/vertex_ai/ocr/deepseek_transformation.py @@ -3,7 +3,7 @@ Vertex AI DeepSeek OCR transformation implementation. """ import json -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict import httpx @@ -38,16 +38,16 @@ class VertexAIDeepSeekOCRConfig(BaseOCRConfig): super().__init__() self.vertex_base = VertexBase() - def get_api_key_env_var(self) -> Optional[str]: + def get_api_key_env_var(self) -> str | None: return VERTEX_AI_DEEPSEEK_OCR_API_KEY_ENV_VAR def validate_environment( self, headers: Dict, model: str, - api_key: Optional[str] = None, - api_base: Optional[str] = None, - litellm_params: Optional[dict] = None, + api_key: str | None = None, + api_base: str | None = None, + litellm_params: dict | None = None, **kwargs, ) -> Dict: """ @@ -89,10 +89,10 @@ class VertexAIDeepSeekOCRConfig(BaseOCRConfig): def get_complete_url( self, - api_base: Optional[str], + api_base: str | None, model: str, optional_params: dict, - litellm_params: Optional[dict] = None, + litellm_params: dict | None = None, **kwargs, ) -> str: """ diff --git a/litellm/llms/vertex_ai/ocr/transformation.py b/litellm/llms/vertex_ai/ocr/transformation.py index f22bf5804af..a725762b3c5 100644 --- a/litellm/llms/vertex_ai/ocr/transformation.py +++ b/litellm/llms/vertex_ai/ocr/transformation.py @@ -2,7 +2,7 @@ Vertex AI Mistral OCR transformation implementation. """ -from typing import Dict, Optional +from typing import Dict from litellm._logging import verbose_logger from litellm.litellm_core_utils.prompt_templates.image_handling import ( @@ -34,16 +34,16 @@ class VertexAIOCRConfig(MistralOCRConfig): super().__init__() self.vertex_base = VertexBase() - def get_api_key_env_var(self) -> Optional[str]: + def get_api_key_env_var(self) -> str | None: return VERTEX_AI_OCR_API_KEY_ENV_VAR def validate_environment( self, headers: Dict, model: str, - api_key: Optional[str] = None, - api_base: Optional[str] = None, - litellm_params: Optional[dict] = None, + api_key: str | None = None, + api_base: str | None = None, + litellm_params: dict | None = None, **kwargs, ) -> Dict: """ @@ -85,10 +85,10 @@ class VertexAIOCRConfig(MistralOCRConfig): def get_complete_url( self, - api_base: Optional[str], + api_base: str | None, model: str, optional_params: dict, - litellm_params: Optional[dict] = None, + litellm_params: dict | None = None, **kwargs, ) -> str: """ diff --git a/litellm/ocr/main.py b/litellm/ocr/main.py index 10580d17f6b..6a196d41768 100644 --- a/litellm/ocr/main.py +++ b/litellm/ocr/main.py @@ -9,7 +9,7 @@ import os import re from dataclasses import dataclass from io import IOBase -from typing import Any, Callable, Coroutine, Optional, Union, cast +from typing import Any, Callable, Coroutine, Union, cast import httpx @@ -38,10 +38,10 @@ base_llm_http_handler = BaseLLMHTTPHandler() class _PreparedOCRRequest: model: str document: dict[str, Any] - api_key: Optional[str] - api_base: Optional[str] + api_key: str | None + api_base: str | None custom_llm_provider: str - extra_headers: Optional[dict[str, object]] + extra_headers: dict[str, object] | None provider_config: BaseOCRConfig optional_params: dict[str, object] litellm_params: dict[str, object] @@ -51,8 +51,8 @@ class _PreparedOCRRequest: @dataclass class _PreparedRustOCRCall: - api_key: Optional[str] - api_base: Optional[str] + api_key: str | None + api_base: str | None headers: dict[str, object] optional_params: dict[str, object] @@ -66,8 +66,8 @@ _RUST_OCR_PROVIDERS = { def _timeout_to_seconds( - timeout: Optional[Union[float, httpx.Timeout]], -) -> Optional[float]: + timeout: Union[float, httpx.Timeout] | None, +) -> float | None: """Convert the Python OCR timeout to a single seconds value for the Rust bridge. The Rust HTTP client takes one duration; ``httpx.Timeout`` carries separate @@ -84,15 +84,15 @@ def _timeout_to_seconds( def _prepare_ocr_request( model: str, document: dict[str, Any], - api_key: Optional[str], - api_base: Optional[str], - timeout: Optional[Union[float, httpx.Timeout]], - custom_llm_provider: Optional[str], - extra_headers: Optional[dict[str, Any]], + api_key: str | None, + api_base: str | None, + timeout: Union[float, httpx.Timeout] | None, + custom_llm_provider: str | None, + extra_headers: dict[str, Any] | None, kwargs: dict[str, Any], ) -> _PreparedOCRRequest: litellm_logging_obj = cast(LiteLLMLoggingObj, kwargs.pop("litellm_logging_obj")) - litellm_call_id = cast(Optional[str], kwargs.get("litellm_call_id", None)) + litellm_call_id = cast(str | None, kwargs.get("litellm_call_id", None)) if not isinstance(document, dict): raise ValueError( @@ -173,7 +173,7 @@ def _prepare_ocr_request( api_key=api_key, api_base=api_base, custom_llm_provider=custom_llm_provider, - extra_headers=cast(Optional[dict[str, object]], extra_headers), + extra_headers=cast(dict[str, object] | None, extra_headers), provider_config=ocr_provider_config, optional_params=cast(dict[str, object], optional_params), litellm_params=dict(litellm_params), @@ -188,7 +188,7 @@ def _rust_ocr_supported(prepared_request: _PreparedOCRRequest) -> bool: def _rust_bridge_optional_params( prepared_request: _PreparedOCRRequest, - resolve_secret: Callable[[str], Optional[str]], + resolve_secret: Callable[[str], str | None], ) -> dict[str, object]: optional_params = dict(prepared_request.optional_params) if prepared_request.custom_llm_provider == "vertex_ai": @@ -214,8 +214,8 @@ def _rust_bridge_optional_params( def _rust_bridge_api_base( prepared_request: _PreparedOCRRequest, - resolve_secret: Callable[[str], Optional[str]], -) -> Optional[str]: + resolve_secret: Callable[[str], str | None], +) -> str | None: if prepared_request.api_base is not None: return prepared_request.api_base if prepared_request.custom_llm_provider == "azure_ai/doc-intelligence": @@ -232,7 +232,7 @@ def _rust_bridge_api_base( def _prepare_rust_ocr_call( prepared_request: _PreparedOCRRequest, - resolve_api_key: Callable[[str], Optional[str]], + resolve_api_key: Callable[[str], str | None], ) -> _PreparedRustOCRCall: provider_config = prepared_request.provider_config api_key_env_var = provider_config.get_api_key_env_var() @@ -280,7 +280,7 @@ def _prepare_rust_ocr_call( def _run_rust_ocr( rust_ocr: RustOcr, prepared_request: _PreparedOCRRequest, - resolve_api_key: Callable[[str], Optional[str]], + resolve_api_key: Callable[[str], str | None], ) -> OCRResponse: """Run the Mistral OCR call through the Rust bridge and wrap the result. @@ -311,7 +311,7 @@ def _run_rust_ocr( async def _run_rust_aocr( rust_aocr: RustAocr, prepared_request: _PreparedOCRRequest, - resolve_api_key: Callable[[str], Optional[str]], + resolve_api_key: Callable[[str], str | None], ) -> OCRResponse: prepared = _prepare_rust_ocr_call( prepared_request=prepared_request, @@ -335,11 +335,11 @@ async def _run_rust_aocr( async def aocr( model: str, document: dict[str, Any], - api_key: Optional[str] = None, - api_base: Optional[str] = None, - timeout: Optional[Union[float, httpx.Timeout]] = None, - custom_llm_provider: Optional[str] = None, - extra_headers: Optional[dict[str, Any]] = None, + api_key: str | None = None, + api_base: str | None = None, + timeout: Union[float, httpx.Timeout] | None = None, + custom_llm_provider: str | None = None, + extra_headers: dict[str, Any] | None = None, **kwargs, ) -> OCRResponse: """ @@ -536,7 +536,7 @@ def convert_file_document_to_url_document(document: dict[str, Any]) -> dict[str, file_bytes: bytes mime_type: str = "application/octet-stream" - file_name: Optional[str] = None + file_name: str | None = None if isinstance(file_input, str): # Bare strings are rejected here. The OCR ``document`` accepts a @@ -606,11 +606,11 @@ def convert_file_document_to_url_document(document: dict[str, Any]) -> dict[str, def ocr( model: str, document: dict[str, Any], - api_key: Optional[str] = None, - api_base: Optional[str] = None, - timeout: Optional[Union[float, httpx.Timeout]] = None, - custom_llm_provider: Optional[str] = None, - extra_headers: Optional[dict[str, Any]] = None, + api_key: str | None = None, + api_base: str | None = None, + timeout: Union[float, httpx.Timeout] | None = None, + custom_llm_provider: str | None = None, + extra_headers: dict[str, Any] | None = None, **kwargs, ) -> Union[OCRResponse, Coroutine[Any, Any, OCRResponse]]: """ diff --git a/tests/test_litellm/ocr/test_rust_bridge.py b/tests/test_litellm/ocr/test_rust_bridge.py index 42de567c453..7e23e441f50 100644 --- a/tests/test_litellm/ocr/test_rust_bridge.py +++ b/tests/test_litellm/ocr/test_rust_bridge.py @@ -732,3 +732,26 @@ def test_ocr_falls_back_to_python_when_bridge_unavailable(monkeypatch): assert captured.get("called") is True # Python path was used assert isinstance(response, OCRResponse) + + +def test_ocr_provider_configs_expose_api_key_env_vars(): + from litellm.llms.azure_ai.ocr.document_intelligence.transformation import ( + AzureDocumentIntelligenceOCRConfig, + ) + from litellm.llms.azure_ai.ocr.transformation import AzureAIOCRConfig + from litellm.llms.base_llm.ocr.transformation import BaseOCRConfig + from litellm.llms.mistral.ocr.transformation import MistralOCRConfig + from litellm.llms.vertex_ai.ocr.deepseek_transformation import ( + VertexAIDeepSeekOCRConfig, + ) + from litellm.llms.vertex_ai.ocr.transformation import VertexAIOCRConfig + + assert BaseOCRConfig().get_api_key_env_var() is None + assert MistralOCRConfig().get_api_key_env_var() == "MISTRAL_API_KEY" + assert AzureAIOCRConfig().get_api_key_env_var() == "AZURE_AI_API_KEY" + assert ( + AzureDocumentIntelligenceOCRConfig().get_api_key_env_var() + == "AZURE_DOCUMENT_INTELLIGENCE_API_KEY" + ) + assert VertexAIOCRConfig().get_api_key_env_var() == "VERTEX_AI_API_KEY" + assert VertexAIDeepSeekOCRConfig().get_api_key_env_var() == "VERTEX_AI_API_KEY"