litellm/tests/test_litellm/responses/test_metadata_codex_callback.py
yuneng-jiang 6a0d03914c
test: drop the cwd-relative sys.path.insert calls from the test suite (#37802)
* 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
2026-08-22 09:25:58 -07:00

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