mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-27 01:22:18 +00:00
* ci: run the unit_selection.sh shard files on every event instead of only fork pull requests Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * ci: rename fork-flag to unit-flag now that it applies on every event * test: move tests/test_litellm root and small trees into tests/unit Pure renames, no content changes. Follow-up commits in this PR fix references, merge the three files that already existed in tests/unit, keep live-provider tests in tests/test_litellm and wire CI. * test: carry tests/test_litellm conftest isolation into tests/unit Callback lists, routing fallbacks, cached HTTP clients, logger state, AWS, proxy-URL and keychain env, and session-end client cleanup now reset for unit tests too. The environment isolation owns its MonkeyPatch so a test's own monkeypatch is undone before the model-cost teardown runs. * test: merge, split and prune the moved root and small-tree tests Merge batches/test_batch_utils.py and the chat_completions and messages dispatch tests into the files that already existed in tests/unit. Keep the live Gemini interactions tests, the async image-fetch format test and the OpenAI embedding scorer test in tests/test_litellm since they need real network or keys. Put test_router.py under tests/unit/test_router so the existing package no longer shadows it. Delete eight tests the audit found superseded by stronger ones kept in this move. * ci: run the moved root and small-tree tests under their legacy flags Add the misc and responses-caching-types flags to unit_selection.sh and CircleCI, extend enterprise-routing and mcp-integration, and point the legacy GHA shards, Makefile, redis-compat workflow, merge smoke manifest and change classifier at the new paths. * test: make the new tests/unit directories packages tests/unit/test_package_layout.py requires every directory to carry an __init__.py, and without one the moved and retained test_litellm_responses_bridge.py modules collide on import. * test: scope the unit socket block to tests/unit in shared sessions The GHA shards collect the legacy test-path and the unit selection in one pytest session. The unit conftest's loopback-only block leaked into legacy modules that reach the network at import. The legacy conftest now lifts the restriction at collect and setup time, and the unit conftest re-applies it when collecting its own modules. * test: give the shard-script tests their own GITHUB_OUTPUT They only passed where the runner set it. The CircleCI unit job's env allowlist drops it, so the script's redirect failed there. * test: point the router and module-deletion checks at tests/unit router_code_coverage and code_qa_check_tests only searched tests/test_litellm, so the moved router tests no longer counted. The two silent-experiment tests the audit deleted were the only direct callers of those methods; they are replaced with tests that assert the forwarded shadow request and the recursion guard. --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
116 lines
4.5 KiB
Python
116 lines
4.5 KiB
Python
"""
|
|
Tests for litellm/vector_stores/main.py.
|
|
|
|
Pins the router threading contract for vector store search: the router is an
|
|
explicit named parameter that reaches the HTTP handler wrapped in the embedding
|
|
executor, and it must never leak into litellm_params/kwargs where logging would
|
|
model_dump() it (the #19550 serialization trap).
|
|
"""
|
|
|
|
import json
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
import litellm.vector_stores.main as vector_stores_main
|
|
from litellm.llms.base_llm.vector_store.transformation import (
|
|
RouterVectorStoreEmbeddingExecutor,
|
|
)
|
|
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
|
from litellm.vector_stores.main import search
|
|
|
|
MOCK_SEARCH_RESPONSE = {
|
|
"object": "vector_store.search_results.page",
|
|
"search_query": "q",
|
|
"data": [],
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize("query", ["q", ["q", "another question"]])
|
|
def test_search_wraps_router_into_the_handler_embedding_executor(query: str | list[str]):
|
|
"""search() hands the HTTP handler a Router-backed embedding executor carrying the
|
|
request metadata, and no bare router kwarg (LIT-6750)"""
|
|
mock_router = MagicMock()
|
|
logger = MagicMock()
|
|
|
|
with (
|
|
patch( # test-quality-ok: stubs provider config resolution; the seam under test is the executor threading
|
|
"litellm.vector_stores.main.ProviderConfigManager.get_provider_vector_stores_config",
|
|
return_value=MagicMock(),
|
|
),
|
|
patch.object( # test-quality-ok: the handler call is the observable boundary for the executor contract
|
|
vector_stores_main.base_llm_http_handler,
|
|
"vector_store_search_handler",
|
|
return_value=MOCK_SEARCH_RESPONSE,
|
|
) as mock_handler,
|
|
):
|
|
response = search(
|
|
vector_store_id="bkt:idx",
|
|
query=query,
|
|
custom_llm_provider="s3_vectors",
|
|
router=mock_router,
|
|
litellm_logging_obj=logger,
|
|
litellm_metadata={"user_api_key_team_id": "team-a"},
|
|
)
|
|
|
|
assert response == MOCK_SEARCH_RESPONSE
|
|
mock_handler.assert_called_once()
|
|
assert "router" not in mock_handler.call_args.kwargs
|
|
assert mock_handler.call_args.kwargs["query"] == query
|
|
executor = mock_handler.call_args.kwargs["embedding_executor"]
|
|
assert isinstance(executor, RouterVectorStoreEmbeddingExecutor)
|
|
assert executor.router is mock_router
|
|
assert dict(executor.metadata) == {"user_api_key_team_id": "team-a"}
|
|
|
|
|
|
def test_search_router_not_in_litellm_params():
|
|
"""Regression (#19550 class): the router must stay out of GenericLiteLLMParams,
|
|
otherwise pre-call logging model_dump()s it and breaks serialization."""
|
|
mock_router = MagicMock()
|
|
logger = MagicMock()
|
|
|
|
with (
|
|
patch( # test-quality-ok: stubs provider config resolution; the seam under test is litellm_params contents
|
|
"litellm.vector_stores.main.ProviderConfigManager.get_provider_vector_stores_config",
|
|
return_value=MagicMock(),
|
|
),
|
|
patch.object( # test-quality-ok: the handler call is where a leaked router in litellm_params would surface
|
|
vector_stores_main.base_llm_http_handler,
|
|
"vector_store_search_handler",
|
|
return_value=MOCK_SEARCH_RESPONSE,
|
|
) as mock_handler,
|
|
):
|
|
search(
|
|
vector_store_id="bkt:idx",
|
|
query="q",
|
|
custom_llm_provider="s3_vectors",
|
|
router=mock_router,
|
|
litellm_logging_obj=logger,
|
|
)
|
|
|
|
litellm_params = mock_handler.call_args.kwargs["litellm_params"]
|
|
assert "router" not in litellm_params.model_dump(exclude_none=True)
|
|
assert getattr(litellm_params, "router", None) is None
|
|
|
|
|
|
def test_search_forwards_top_level_user_context_to_bedrock_retrieve():
|
|
"""Regression (LIT-4415): a top-level userContext, the shape the OpenAI SDK's extra_body
|
|
produces on the proxy path, reaches the Bedrock Retrieve request body."""
|
|
client = MagicMock(spec=HTTPHandler)
|
|
client.post.return_value = MagicMock(status_code=200, json=MagicMock(return_value={"retrievalResults": []}))
|
|
|
|
search(
|
|
vector_store_id="kb123",
|
|
query="q",
|
|
custom_llm_provider="bedrock",
|
|
aws_region_name="us-west-2",
|
|
aws_access_key_id="test-key-id",
|
|
aws_secret_access_key="test-secret-key",
|
|
userContext={"userId": "alice@example.com"},
|
|
client=client,
|
|
litellm_logging_obj=MagicMock(),
|
|
)
|
|
|
|
posted = json.loads(client.post.call_args.kwargs["data"])
|
|
assert posted["userContext"] == {"userId": "alice@example.com"}
|
|
assert posted["retrievalQuery"] == {"text": "q"}
|