mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-12 19:04:59 +00:00
727ab8dcc4
The Python 3.13 CCI smoke matrix surfaces a partially-initialized-module
ImportError when loading the managed files hook chain:
litellm.proxy.hooks/__init__ (mid-import)
-> enterprise.enterprise_hooks
-> litellm_enterprise.proxy.hooks.managed_files
-> litellm.llms.base_llm.managed_resources.isolation
-> litellm.proxy.management_endpoints.common_utils
-> litellm.proxy.utils (re-enters litellm.proxy.hooks)
The except ImportError block in hooks/__init__.py silently swallowed the
failure, leaving managed_files unregistered and POST /files returning
500 "Managed files hook not found".
Two-layer fix:
- Inline the 3-line _user_has_admin_view check in isolation.py instead
of importing it from litellm.proxy.management_endpoints.common_utils.
litellm.llms.* should not depend on litellm.proxy.* — removing this
layering violation breaks the cycle at its root.
- Define PROXY_HOOKS and get_proxy_hook before the conditional
enterprise import in litellm/proxy/hooks/__init__.py, so any future
re-entry resolves the public names instead of hitting an
ImportError on a partially-initialized module.
Also fold in two unrelated CCI repairs surfaced in the same staging run:
- tests/otel_tests/test_key_logging_callbacks.py: per-key
gcs_bucket_name / gcs_path_service_account are now stripped by
initialize_dynamic_callback_params, so the GCS client falls through
to the env-only branch. Update the assertion to match the new
"GCS_BUCKET_NAME is not set" message.
- .circleci/config.yml: tests/pass_through_tests now resolves
google-auth-library@10.x via the @google-cloud/vertexai 1.12.0 bump,
which uses dynamic ESM imports Jest 29 cannot load without
--experimental-vm-modules. Pass that flag in the Vertex JS test step.
Adds tests/test_litellm/proxy/hooks/test_proxy_hooks_init.py as a
regression guard: managed_files / managed_vector_stores must register,
and isolation.py must not transitively import litellm.proxy.utils.
70 lines
2.1 KiB
Python
70 lines
2.1 KiB
Python
"""
|
|
Tests for Key based logging callbacks
|
|
|
|
"""
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.asyncio()
|
|
async def test_key_logging_callbacks():
|
|
"""
|
|
Create virtual key with a logging callback set on the key
|
|
Call /key/health for the key -> it should be unhealthy
|
|
"""
|
|
# Generate a key with logging callback
|
|
generate_url = "http://0.0.0.0:4000/key/generate"
|
|
generate_headers = {
|
|
"Authorization": "Bearer sk-1234",
|
|
"Content-Type": "application/json",
|
|
}
|
|
generate_payload = {
|
|
"metadata": {
|
|
"logging": [
|
|
{
|
|
"callback_name": "gcs_bucket",
|
|
"callback_type": "success_and_failure",
|
|
"callback_vars": {
|
|
"gcs_bucket_name": "key-logging-project1",
|
|
"gcs_path_service_account": "bad-service-account",
|
|
},
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
generate_response = await client.post(
|
|
generate_url, headers=generate_headers, json=generate_payload
|
|
)
|
|
|
|
assert generate_response.status_code == 200
|
|
generate_data = generate_response.json()
|
|
assert "key" in generate_data
|
|
|
|
_key = generate_data["key"]
|
|
|
|
# Check key health
|
|
health_url = "http://localhost:4000/key/health"
|
|
health_headers = {
|
|
"Authorization": f"Bearer {_key}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
health_response = await client.post(health_url, headers=health_headers, json={})
|
|
|
|
assert health_response.status_code == 200
|
|
health_data = health_response.json()
|
|
print("key_health_data", health_data)
|
|
# Check the response format and content
|
|
assert "key" in health_data
|
|
assert "logging_callbacks" in health_data
|
|
assert health_data["logging_callbacks"]["callbacks"] == ["gcs_bucket"]
|
|
assert health_data["logging_callbacks"]["status"] == "unhealthy"
|
|
assert (
|
|
"GCS_BUCKET_NAME is not set in the environment"
|
|
in health_data["logging_callbacks"]["details"]
|
|
)
|