mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
* test: drop the cwd-relative sys.path.insert calls from the test suite
TQ003 stands at 1,077 across 1,058 files, and 1,015 of them are the same shape:
sys.path.insert(0, os.path.abspath("../..")) and its deeper siblings. The
argument resolves against the working directory rather than the file, so from
the repo root, where every job runs pytest, it inserts the directory two levels
above the checkout. It has never pointed at litellm. The package is installed
into the environment anyway, which is what actually makes the import work, and
what the rule's message has said all along.
Removing them leaves 1,634 imports of sys and os with no remaining reference,
and those go too, except where another test module imports the name back out of
the file. The rest of TQ003 is 62 call sites that resolve against __file__ or a
variable, which are a different question and are left alone.
Collection is identical either way: 45,871 tests and the same 51 pre-existing
collection errors before and after, and ruff reports no new undefined name.
* test: drop the duplicate imports the sys.path sweep exposed to F811
* test(pre-call-utils): restore the os import the new bedrock tests need
180 lines
5.8 KiB
Python
180 lines
5.8 KiB
Python
"""
|
|
Test that metadata is passed to custom callbacks during chat completion calls to codex models.
|
|
|
|
Fixes issue: Metadata is no longer passed to custom callback during chat completion
|
|
calls to codex models (#21204)
|
|
|
|
Codex models (gpt-5.1-codex, gpt-5.2-codex) use mode=responses and route through
|
|
responses_api_bridge. The bridge converts metadata to litellm_metadata. This test
|
|
verifies metadata is preserved for custom callbacks via kwargs['litellm_params']['metadata'].
|
|
"""
|
|
|
|
import asyncio
|
|
from typing import Optional
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
|
|
import pytest
|
|
|
|
import litellm
|
|
from litellm.integrations.custom_logger import CustomLogger
|
|
|
|
|
|
def _make_mock_http_response(response_dict: dict):
|
|
"""Create a mock HTTP response that returns response_dict from .json()."""
|
|
|
|
class MockResponse:
|
|
def __init__(self, json_data, status_code=200):
|
|
self._json_data = json_data
|
|
self.status_code = status_code
|
|
self.text = str(json_data)
|
|
self.headers = {}
|
|
|
|
def json(self):
|
|
return self._json_data
|
|
|
|
return MockResponse(response_dict, 200)
|
|
|
|
|
|
class MetadataCaptureCallback(CustomLogger):
|
|
"""Custom callback that captures kwargs passed to async_log_success_event."""
|
|
|
|
def __init__(self):
|
|
self.captured_kwargs: Optional[dict] = None
|
|
self.event = asyncio.Event()
|
|
|
|
async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
|
|
self.captured_kwargs = kwargs
|
|
self.event.set()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_metadata_passed_to_custom_callback_codex_models():
|
|
"""
|
|
Test that metadata passed to completion() is available in custom callback
|
|
when using codex models (responses API bridge path).
|
|
|
|
Codex models have mode=responses and route through responses_api_bridge,
|
|
which passes litellm_metadata. The fix ensures this is preserved as
|
|
litellm_params.metadata for callback compatibility.
|
|
"""
|
|
from litellm.types.llms.openai import ResponsesAPIResponse
|
|
|
|
mock_response = ResponsesAPIResponse.model_construct(
|
|
id="resp-test",
|
|
created_at=0,
|
|
output=[
|
|
{
|
|
"type": "message",
|
|
"id": "msg-1",
|
|
"status": "completed",
|
|
"role": "assistant",
|
|
"content": [{"type": "output_text", "text": "Hello!"}],
|
|
}
|
|
],
|
|
object="response",
|
|
model="gpt-5.1-codex",
|
|
status="completed",
|
|
usage={
|
|
"input_tokens": 5,
|
|
"output_tokens": 10,
|
|
"total_tokens": 15,
|
|
},
|
|
)
|
|
|
|
test_metadata = {"foo": "bar", "trace_id": "test-123"}
|
|
callback = MetadataCaptureCallback()
|
|
original_callbacks = litellm.callbacks.copy() if litellm.callbacks else []
|
|
litellm.callbacks = [callback]
|
|
|
|
try:
|
|
with patch(
|
|
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
|
new_callable=AsyncMock,
|
|
) as mock_post:
|
|
mock_post.return_value = _make_mock_http_response(
|
|
mock_response.model_dump()
|
|
)
|
|
# gpt-5.1-codex has mode=responses - routes through responses bridge
|
|
await litellm.acompletion(
|
|
model="gpt-5.1-codex",
|
|
messages=[{"role": "user", "content": "Hello"}],
|
|
metadata=test_metadata,
|
|
)
|
|
|
|
await asyncio.wait_for(callback.event.wait(), timeout=5.0)
|
|
|
|
assert callback.captured_kwargs is not None, "Callback should have been invoked"
|
|
|
|
litellm_params = callback.captured_kwargs.get("litellm_params", {})
|
|
metadata = litellm_params.get("metadata") or {}
|
|
|
|
assert "foo" in metadata, "metadata['foo'] should be accessible in callback"
|
|
assert metadata["foo"] == "bar"
|
|
assert metadata.get("trace_id") == "test-123"
|
|
finally:
|
|
litellm.callbacks = original_callbacks
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_metadata_passed_via_litellm_metadata_responses_api():
|
|
"""
|
|
Test that when calling responses() directly with litellm_metadata,
|
|
metadata is preserved for custom callbacks.
|
|
|
|
Uses HTTP mock since mock_response returns early before update_environment_variables.
|
|
"""
|
|
from litellm.types.llms.openai import ResponsesAPIResponse
|
|
|
|
mock_response = ResponsesAPIResponse.model_construct(
|
|
id="resp-test-2",
|
|
created_at=0,
|
|
output=[
|
|
{
|
|
"type": "message",
|
|
"id": "msg-2",
|
|
"status": "completed",
|
|
"role": "assistant",
|
|
"content": [{"type": "output_text", "text": "Hi there!"}],
|
|
}
|
|
],
|
|
object="response",
|
|
model="gpt-4o",
|
|
status="completed",
|
|
usage={
|
|
"input_tokens": 2,
|
|
"output_tokens": 3,
|
|
"total_tokens": 5,
|
|
},
|
|
)
|
|
|
|
test_metadata = {"request_id": "req-456"}
|
|
callback = MetadataCaptureCallback()
|
|
original_callbacks = litellm.callbacks.copy() if litellm.callbacks else []
|
|
litellm.callbacks = [callback]
|
|
|
|
try:
|
|
with patch(
|
|
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
|
new_callable=AsyncMock,
|
|
) as mock_post:
|
|
mock_post.return_value = _make_mock_http_response(
|
|
mock_response.model_dump()
|
|
)
|
|
await litellm.aresponses(
|
|
model="gpt-4o",
|
|
input="hi",
|
|
litellm_metadata=test_metadata,
|
|
)
|
|
|
|
await asyncio.wait_for(callback.event.wait(), timeout=5.0)
|
|
|
|
assert callback.captured_kwargs is not None
|
|
|
|
litellm_params = callback.captured_kwargs.get("litellm_params", {})
|
|
metadata = litellm_params.get("metadata") or {}
|
|
|
|
assert "request_id" in metadata
|
|
assert metadata["request_id"] == "req-456"
|
|
finally:
|
|
litellm.callbacks = original_callbacks
|