BerriAI/litellm · error · TypeError

Azure AD token must be a string, got {type(token)}

Error message

Azure AD token must be a string, got {type(token)}

What it means

When a custom azure_ad_token_provider callable is used, LiteLLM invokes it and requires the return value to be a str. A non-string (None, bytes, dict, azure.core.credentials.AccessToken object) raises TypeError('Azure AD token must be a string, got {type}'). This guards the SDK before the value is placed in an Authorization header.

Source

Thrown at litellm/llms/azure/common_utils.py:373

            )
            raise e

        #########################################################
        # If litellm.enable_azure_ad_token_refresh is True and no other token provider is available,
        # try to get DefaultAzureCredential provider
        #########################################################
        if azure_ad_token_provider is None and azure_ad_token is None:
            azure_ad_token_provider = BaseAzureLLM._try_get_default_azure_credential_provider(
                scope=scope,
            )

    # Execute the token provider to get the token if available
    if azure_ad_token_provider and callable(azure_ad_token_provider):
        try:
            token: Final = azure_ad_token_provider()
            if not isinstance(token, str):
                verbose_logger.error("Azure AD token provider returned non-string value: %s", type(token))
                raise TypeError(f"Azure AD token must be a string, got {type(token)}")
            else:
                azure_ad_token = token
        except TypeError:
            # Re-raise TypeError directly
            raise
        except Exception as e:
            verbose_logger.error("Error calling Azure AD token provider: %s", e)
            raise RuntimeError(f"Failed to get Azure AD token: {e}") from e

    return azure_ad_token


class BaseAzureLLM(BaseOpenAILLM):
    @staticmethod
    def _try_get_default_azure_credential_provider(
        scope: str,
    ) -> Callable[[], str] | None:
        """

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Make the provider return only the string: lambda: cred.get_token(scope).token.
  2. If the provider is async, either await it inside a sync wrapper or use the async call path that supports it.
  3. Add a None check inside your provider and raise a clear error there instead of returning None.

Example fix

# before
def provider():
    return DefaultAzureCredential().get_token("https://cognitiveservices.azure.com/.default")  # AccessToken object

# after
def provider():
    return DefaultAzureCredential().get_token("https://cognitiveservices.azure.com/.default").token  # str
Defensive patterns

Strategy: type-guard

Validate before calling

def checked_provider(provider):
    token = provider()
    if not isinstance(token, str) or not token:
        raise TypeError(f"provider returned {type(token).__name__}, expected str")
    return token

Type guard

from typing import Callable, TypeGuard

def is_string_provider(p: Callable[[], object]) -> TypeGuard[Callable[[], str]]:
    result = p()
    return isinstance(result, str) and len(result) > 0

Try / catch

try:
    resp = litellm.completion(..., azure_ad_token_provider=provider)
except TypeError as e:
    if "must be a string" in str(e):
        # unwrap azure-identity AccessToken: provider returned AccessToken, need .token
        raise RuntimeError("Fix provider to return credential.get_token(scope).token") from e
    raise

Prevention

When it happens

Trigger: Passing a provider like lambda: azure_identity.get_certificate_credential(...).get_token(scope) that returns an AccessToken object, not .token; returning None when the credential chain fails silently; returning bytes from a secret fetch.

Common situations: Wrapping azure-identity credentials: developers return credential.get_token(scope) instead of credential.get_token(scope).token; async providers (returning a coroutine) passed to the sync path; caching layers that store the whole token response dict.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/37679c5c6368be7a. Report an issue: GitHub.