Zum Inhalt

tests/test_client.py

from types import SimpleNamespace

from touchstone.client import OpenAIStreamClient


def test_client_yields_reasoning_and_content_from_delta(monkeypatch):
    client = OpenAIStreamClient("http://localhost:9/v1")
    # chunk 1: only reasoning_content; chunk 2: only content; chunk 3: usage
    chunks = [
        SimpleNamespace(
            choices=[
                SimpleNamespace(delta=SimpleNamespace(content=None, reasoning_content="think"))
            ],
            usage=None,
        ),
        SimpleNamespace(
            choices=[SimpleNamespace(delta=SimpleNamespace(content="hi"))],
            usage=None,
        ),
        SimpleNamespace(
            choices=[],
            usage=SimpleNamespace(prompt_tokens=3, completion_tokens=1),
        ),
    ]
    monkeypatch.setattr(client._client.chat.completions, "create", lambda **kw: chunks)

    evs = list(client.stream(messages=[], model="m", max_tokens=1, temperature=0.0, seed=0))
    assert any(e.reasoning_text == "think" for e in evs)
    assert any(e.delta_text == "hi" for e in evs)
    assert any(e.completion_tokens == 1 for e in evs)


def test_client_supports_reasoning_field_alias(monkeypatch):
    client = OpenAIStreamClient("http://localhost:9/v1")
    chunk = SimpleNamespace(
        choices=[SimpleNamespace(delta=SimpleNamespace(content=None, reasoning="hmm"))],
        usage=None,
    )
    monkeypatch.setattr(client._client.chat.completions, "create", lambda **kw: [chunk])
    evs = list(client.stream(messages=[], model="m", max_tokens=1, temperature=0.0, seed=0))
    assert any(e.reasoning_text == "hmm" for e in evs)


def test_stream_forwards_extra_body_only_when_set(monkeypatch):
    from touchstone.client import OpenAIStreamClient

    captured = {}

    class _FakeCreate:
        def __call__(self, **kwargs):
            captured.update(kwargs)
            return iter([])  # empty stream

    class _FakeOpenAI:
        def __init__(self, **_):
            self.chat = type("C", (), {"completions": type("X", (), {"create": _FakeCreate()})()})()
            self.models = None

    monkeypatch.setattr("openai.OpenAI", _FakeOpenAI)
    c = OpenAIStreamClient("http://x/v1")

    list(
        c.stream(
            messages=[{"role": "user", "content": "hi"}],
            model="m",
            max_tokens=10,
            temperature=0.0,
            seed=42,
        )
    )
    assert "extra_body" not in captured  # not set → not forwarded (no SDK default override)

    captured.clear()
    list(
        c.stream(
            messages=[{"role": "user", "content": "hi"}],
            model="m",
            max_tokens=10,
            temperature=0.0,
            seed=42,
            extra_body={"chat_template_kwargs": {"enable_thinking": False}},
        )
    )
    assert captured["extra_body"] == {"chat_template_kwargs": {"enable_thinking": False}}


def test_stream_omits_max_tokens_when_none(monkeypatch):
    from touchstone.client import OpenAIStreamClient

    captured = {}

    class _FakeCreate:
        def __call__(self, **kwargs):
            captured.update(kwargs)
            return iter([])  # empty stream

    class _FakeOpenAI:
        def __init__(self, **_):
            self.chat = type("C", (), {"completions": type("X", (), {"create": _FakeCreate()})()})()
            self.models = None

    monkeypatch.setattr("openai.OpenAI", _FakeOpenAI)
    c = OpenAIStreamClient("http://x/v1")

    # max_tokens=None → no cap sent → the server answers freely (eval default)
    list(
        c.stream(
            messages=[{"role": "user", "content": "hi"}],
            model="m",
            max_tokens=None,
            temperature=0.0,
            seed=42,
        )
    )
    assert "max_tokens" not in captured

    captured.clear()
    list(
        c.stream(
            messages=[{"role": "user", "content": "hi"}],
            model="m",
            max_tokens=256,
            temperature=0.0,
            seed=42,
        )
    )
    assert captured["max_tokens"] == 256