mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
Merge f843e596e7 into eddfb5fb20
This commit is contained in:
commit
25ad6b7c9d
2 changed files with 191 additions and 6 deletions
|
|
@ -48,6 +48,7 @@ from litellm.exceptions import (
|
|||
ServiceUnavailableError,
|
||||
)
|
||||
from litellm.integrations.custom_logger import CustomLogger, Span
|
||||
from litellm.litellm_core_utils.credential_accessor import CredentialAccessor
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
encrypted_content_of_block,
|
||||
strip_encrypted_reasoning_from_messages,
|
||||
|
|
@ -215,11 +216,11 @@ class EncryptedContentAffinityCheck(CustomLogger):
|
|||
@staticmethod
|
||||
def _encryption_boundary_key(
|
||||
litellm_params: object,
|
||||
) -> tuple | None:
|
||||
) -> tuple[object, object] | None:
|
||||
"""
|
||||
``(api_base, api_key)`` pair identifying an Azure resource. Two
|
||||
deployments sharing both are interchangeable for ``encrypted_content``
|
||||
follow-ups; Azure rejects content produced by any other resource.
|
||||
``(api_base, api_key)`` identifies an upstream encryption boundary.
|
||||
The values are resolved from the deployment and its named credential
|
||||
without modifying the deployment.
|
||||
|
||||
Accepts any object exposing dict-style ``.get(key, default)``: plain
|
||||
dicts (the common case in ``healthy_deployments``) as well as
|
||||
|
|
@ -234,9 +235,25 @@ class EncryptedContentAffinityCheck(CustomLogger):
|
|||
return None
|
||||
api_base: Final = getter("api_base")
|
||||
api_key: Final = getter("api_key")
|
||||
if not api_base or not api_key:
|
||||
credential_name: Final = getter("litellm_credential_name")
|
||||
credential_values: Final[Mapping[str, object] | None] = (
|
||||
CredentialAccessor.get_credential_values(credential_name)
|
||||
if isinstance(credential_name, str) and credential_name
|
||||
else None
|
||||
)
|
||||
effective_api_base: Final = (
|
||||
credential_values.get("api_base")
|
||||
if credential_values is not None and "api_base" in credential_values
|
||||
else api_base
|
||||
)
|
||||
effective_api_key: Final = (
|
||||
credential_values.get("api_key")
|
||||
if credential_values is not None and "api_key" in credential_values
|
||||
else api_key
|
||||
)
|
||||
if not effective_api_base or not effective_api_key:
|
||||
return None
|
||||
return (api_base, api_key)
|
||||
return (effective_api_base, effective_api_key)
|
||||
|
||||
def _find_deployments_on_same_encryption_boundary(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from unittest.mock import AsyncMock, patch
|
|||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.models.credentials import CredentialItem
|
||||
from litellm.responses.utils import ResponsesAPIRequestUtils
|
||||
from litellm.types.llms.openai import ResponsesAPIResponse
|
||||
|
||||
|
|
@ -1082,6 +1083,173 @@ def test_boundary_key_accepts_pydantic_litellm_params_instance():
|
|||
)
|
||||
|
||||
|
||||
def test_boundary_key_resolves_missing_values_from_named_credential():
|
||||
from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import (
|
||||
EncryptedContentAffinityCheck,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object( # test-quality-ok: credential registry is the direct dependency under test
|
||||
litellm,
|
||||
"credential_list",
|
||||
[
|
||||
CredentialItem(
|
||||
credential_name="account-a",
|
||||
credential_values={
|
||||
"api_base": "https://account-a.example.com",
|
||||
"api_key": "credential-key-a",
|
||||
},
|
||||
credential_info={},
|
||||
)
|
||||
],
|
||||
)
|
||||
):
|
||||
boundary = EncryptedContentAffinityCheck._encryption_boundary_key({"litellm_credential_name": "account-a"})
|
||||
|
||||
assert boundary == ("https://account-a.example.com", "credential-key-a")
|
||||
|
||||
|
||||
def test_boundary_key_matches_named_credential_precedence():
|
||||
from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import (
|
||||
EncryptedContentAffinityCheck,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object( # test-quality-ok: credential registry is the direct dependency under test
|
||||
litellm,
|
||||
"credential_list",
|
||||
[
|
||||
CredentialItem(
|
||||
credential_name="account-a",
|
||||
credential_values={
|
||||
"api_base": "https://credential.example.com",
|
||||
"api_key": "credential-key-a",
|
||||
},
|
||||
credential_info={},
|
||||
)
|
||||
],
|
||||
)
|
||||
):
|
||||
boundary = EncryptedContentAffinityCheck._encryption_boundary_key(
|
||||
{
|
||||
"api_base": "https://deployment.example.com",
|
||||
"api_key": "deployment-key",
|
||||
"litellm_credential_name": "account-a",
|
||||
}
|
||||
)
|
||||
|
||||
assert boundary == ("https://credential.example.com", "credential-key-a")
|
||||
|
||||
|
||||
def test_boundary_key_resolves_credential_when_explicit_values_are_empty():
|
||||
from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import (
|
||||
EncryptedContentAffinityCheck,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object( # test-quality-ok: credential registry is the direct dependency under test
|
||||
litellm,
|
||||
"credential_list",
|
||||
[
|
||||
CredentialItem(
|
||||
credential_name="account-a",
|
||||
credential_values={
|
||||
"api_base": "https://credential.example.com",
|
||||
"api_key": "credential-key-a",
|
||||
},
|
||||
credential_info={},
|
||||
)
|
||||
],
|
||||
)
|
||||
):
|
||||
boundary = EncryptedContentAffinityCheck._encryption_boundary_key(
|
||||
{
|
||||
"api_base": "",
|
||||
"api_key": "",
|
||||
"litellm_credential_name": "account-a",
|
||||
}
|
||||
)
|
||||
|
||||
assert boundary == ("https://credential.example.com", "credential-key-a")
|
||||
|
||||
|
||||
def test_boundary_fallback_matches_deployments_with_same_named_credential_values():
|
||||
from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import (
|
||||
EncryptedContentAffinityCheck,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object( # test-quality-ok: credential registry is the direct dependency under test
|
||||
litellm,
|
||||
"credential_list",
|
||||
[
|
||||
CredentialItem(
|
||||
credential_name="account-a",
|
||||
credential_values={
|
||||
"api_base": "https://account-a.example.com",
|
||||
"api_key": "credential-key-a",
|
||||
},
|
||||
credential_info={},
|
||||
),
|
||||
CredentialItem(
|
||||
credential_name="account-a-peer",
|
||||
credential_values={
|
||||
"api_base": "https://account-a.example.com",
|
||||
"api_key": "credential-key-a",
|
||||
},
|
||||
credential_info={},
|
||||
),
|
||||
CredentialItem(
|
||||
credential_name="account-b",
|
||||
credential_values={
|
||||
"api_base": "https://account-b.example.com",
|
||||
"api_key": "credential-key-b",
|
||||
},
|
||||
credential_info={},
|
||||
),
|
||||
],
|
||||
)
|
||||
):
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "gpt-5.3-codex",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-5.3-codex",
|
||||
"litellm_credential_name": "account-a",
|
||||
},
|
||||
"model_info": {"id": "origin"},
|
||||
}
|
||||
],
|
||||
num_retries=0,
|
||||
)
|
||||
check = EncryptedContentAffinityCheck(router=router)
|
||||
healthy_deployments = [
|
||||
{
|
||||
"model_info": {"id": "peer-same-boundary"},
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-5.4",
|
||||
"litellm_credential_name": "account-a-peer",
|
||||
},
|
||||
},
|
||||
{
|
||||
"model_info": {"id": "peer-different-boundary"},
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-5.4",
|
||||
"litellm_credential_name": "account-b",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
matches, originating = check._find_deployments_on_same_encryption_boundary(
|
||||
healthy_deployments=healthy_deployments,
|
||||
model_id="origin",
|
||||
)
|
||||
|
||||
assert originating is not None
|
||||
assert [deployment["model_info"]["id"] for deployment in matches] == ["peer-same-boundary"]
|
||||
|
||||
|
||||
def test_boundary_key_rejects_non_dict_like_inputs():
|
||||
"""
|
||||
Inputs that don't expose ``.get()`` (None, lists, strings, ints) -> None.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue