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
- Make the provider return only the string: lambda: cred.get_token(scope).token.
- If the provider is async, either await it inside a sync wrapper or use the async call path that supports it.
- 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
- Always return cred.get_token(scope).token from azure-identity wrappers.
- Unit-test custom providers: assert isinstance(result, str).
- Never return the full token response object or bytes from a provider.
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
- api_key is required for Azure AI Speech transcription.
- max retries must be an int
- AZURE_CLIENT_ID and AZURE_TENANT_ID must be set
- OIDC token could not be retrieved from secret manager.
- {req_token.text}
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/37679c5c6368be7a.
Report an issue: GitHub.