|  | 
|  | 1 | +from __future__ import annotations | 
|  | 2 | + | 
|  | 3 | +from typing import Any | 
|  | 4 | + | 
|  | 5 | +import pytest | 
|  | 6 | + | 
|  | 7 | +from agents import ModelSettings, ModelTracing, __version__ | 
|  | 8 | +from agents.models.chatcmpl_helpers import USER_AGENT_OVERRIDE | 
|  | 9 | + | 
|  | 10 | + | 
|  | 11 | +@pytest.mark.allow_call_model_methods | 
|  | 12 | +@pytest.mark.asyncio | 
|  | 13 | +@pytest.mark.parametrize("override_ua", [None, "test_user_agent"]) | 
|  | 14 | +async def test_user_agent_header_litellm(override_ua: str | None, monkeypatch): | 
|  | 15 | +    called_kwargs: dict[str, Any] = {} | 
|  | 16 | +    expected_ua = override_ua or f"Agents/Python {__version__}" | 
|  | 17 | + | 
|  | 18 | +    import importlib | 
|  | 19 | +    import sys | 
|  | 20 | +    import types as pytypes | 
|  | 21 | + | 
|  | 22 | +    litellm_fake: Any = pytypes.ModuleType("litellm") | 
|  | 23 | + | 
|  | 24 | +    class DummyMessage: | 
|  | 25 | +        role = "assistant" | 
|  | 26 | +        content = "Hello" | 
|  | 27 | +        tool_calls: list[Any] | None = None | 
|  | 28 | + | 
|  | 29 | +        def get(self, _key, _default=None): | 
|  | 30 | +            return None | 
|  | 31 | + | 
|  | 32 | +        def model_dump(self): | 
|  | 33 | +            return {"role": self.role, "content": self.content} | 
|  | 34 | + | 
|  | 35 | +    class Choices:  # noqa: N801 - mimic litellm naming | 
|  | 36 | +        def __init__(self): | 
|  | 37 | +            self.message = DummyMessage() | 
|  | 38 | + | 
|  | 39 | +    class DummyModelResponse: | 
|  | 40 | +        def __init__(self): | 
|  | 41 | +            self.choices = [Choices()] | 
|  | 42 | + | 
|  | 43 | +    async def acompletion(**kwargs): | 
|  | 44 | +        nonlocal called_kwargs | 
|  | 45 | +        called_kwargs = kwargs | 
|  | 46 | +        return DummyModelResponse() | 
|  | 47 | + | 
|  | 48 | +    utils_ns = pytypes.SimpleNamespace() | 
|  | 49 | +    utils_ns.Choices = Choices | 
|  | 50 | +    utils_ns.ModelResponse = DummyModelResponse | 
|  | 51 | + | 
|  | 52 | +    litellm_types = pytypes.SimpleNamespace( | 
|  | 53 | +        utils=utils_ns, | 
|  | 54 | +        llms=pytypes.SimpleNamespace(openai=pytypes.SimpleNamespace(ChatCompletionAnnotation=dict)), | 
|  | 55 | +    ) | 
|  | 56 | +    litellm_fake.acompletion = acompletion | 
|  | 57 | +    litellm_fake.types = litellm_types | 
|  | 58 | + | 
|  | 59 | +    monkeypatch.setitem(sys.modules, "litellm", litellm_fake) | 
|  | 60 | + | 
|  | 61 | +    litellm_mod = importlib.import_module("agents.extensions.models.litellm_model") | 
|  | 62 | +    monkeypatch.setattr(litellm_mod, "litellm", litellm_fake, raising=True) | 
|  | 63 | +    LitellmModel = litellm_mod.LitellmModel | 
|  | 64 | + | 
|  | 65 | +    model = LitellmModel(model="gpt-4") | 
|  | 66 | + | 
|  | 67 | +    if override_ua is not None: | 
|  | 68 | +        token = USER_AGENT_OVERRIDE.set(override_ua) | 
|  | 69 | +    else: | 
|  | 70 | +        token = None | 
|  | 71 | +    try: | 
|  | 72 | +        await model.get_response( | 
|  | 73 | +            system_instructions=None, | 
|  | 74 | +            input="hi", | 
|  | 75 | +            model_settings=ModelSettings(), | 
|  | 76 | +            tools=[], | 
|  | 77 | +            output_schema=None, | 
|  | 78 | +            handoffs=[], | 
|  | 79 | +            tracing=ModelTracing.DISABLED, | 
|  | 80 | +            previous_response_id=None, | 
|  | 81 | +            conversation_id=None, | 
|  | 82 | +            prompt=None, | 
|  | 83 | +        ) | 
|  | 84 | +    finally: | 
|  | 85 | +        if token is not None: | 
|  | 86 | +            USER_AGENT_OVERRIDE.reset(token) | 
|  | 87 | + | 
|  | 88 | +    assert "extra_headers" in called_kwargs | 
|  | 89 | +    assert called_kwargs["extra_headers"]["User-Agent"] == expected_ua | 
0 commit comments