e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
176 lines
5.5 KiB
Python
176 lines
5.5 KiB
Python
"""Tests for the LLM client wrapper."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from _pytest.monkeypatch import MonkeyPatch
|
|
import pytest
|
|
|
|
from deeptutor.services.llm.client import LLMClient
|
|
from deeptutor.services.llm.config import LLMConfig
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_complete_uses_factory(monkeypatch: MonkeyPatch) -> None:
|
|
"""Client complete should delegate to factory.complete."""
|
|
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
|
|
client = LLMClient(config)
|
|
|
|
async def _fake_complete(**_kwargs: object) -> str:
|
|
return "ok"
|
|
|
|
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
|
|
|
|
result = await client.complete("hello")
|
|
|
|
assert result == "ok"
|
|
|
|
|
|
def test_client_complete_sync(monkeypatch: MonkeyPatch) -> None:
|
|
"""complete_sync should run in a fresh event loop."""
|
|
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
|
|
client = LLMClient(config)
|
|
|
|
async def _fake_complete(
|
|
_prompt: str,
|
|
_system_prompt: str | None = None,
|
|
_history: list[dict[str, str]] | None = None,
|
|
**_kwargs: object,
|
|
) -> str:
|
|
return "ok"
|
|
|
|
monkeypatch.setattr(client, "complete", _fake_complete)
|
|
|
|
assert client.complete_sync("hello") == "ok"
|
|
|
|
|
|
def test_client_reports_multimodal_image_support() -> None:
|
|
assert (
|
|
LLMClient(
|
|
LLMConfig(model="gpt-4o", api_key="key", base_url="https://example.com")
|
|
).supports_multimodal_images()
|
|
is True
|
|
)
|
|
assert (
|
|
LLMClient(
|
|
LLMConfig(model="gpt-3.5-turbo", api_key="key", base_url="https://example.com")
|
|
).supports_multimodal_images()
|
|
is False
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_complete_sync_running_loop() -> None:
|
|
"""complete_sync should raise when called from a running event loop."""
|
|
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
|
|
client = LLMClient(config)
|
|
|
|
with pytest.raises(RuntimeError):
|
|
client.complete_sync("hello")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_get_model_func_uses_factory(monkeypatch: MonkeyPatch) -> None:
|
|
"""get_model_func should append prompt after history messages."""
|
|
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
|
|
client = LLMClient(config)
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
async def _fake_complete(**kwargs: object) -> str:
|
|
captured.update(kwargs)
|
|
return "ok"
|
|
|
|
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
|
|
|
|
func = client.get_model_func()
|
|
result = await func(
|
|
"hello",
|
|
system_prompt="sys",
|
|
history_messages=[{"role": "user", "content": "old"}],
|
|
)
|
|
|
|
assert result == "ok"
|
|
assert captured["prompt"] == "hello"
|
|
assert captured["system_prompt"] == "sys"
|
|
assert captured["messages"] == [
|
|
{"role": "system", "content": "sys"},
|
|
{"role": "user", "content": "old"},
|
|
{"role": "user", "content": "hello"},
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_get_model_func_empty_history_uses_prompt(
|
|
monkeypatch: MonkeyPatch,
|
|
) -> None:
|
|
"""Empty history_messages must not override the current prompt."""
|
|
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
|
|
client = LLMClient(config)
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
async def _fake_complete(**kwargs: object) -> str:
|
|
captured.update(kwargs)
|
|
return "ok"
|
|
|
|
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
|
|
|
|
func = client.get_model_func()
|
|
result = await func("hello", system_prompt="sys", history_messages=[])
|
|
|
|
assert result == "ok"
|
|
assert captured["prompt"] == "hello"
|
|
assert captured["system_prompt"] == "sys"
|
|
assert captured["messages"] is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_get_model_func_explicit_messages_override_prompt(
|
|
monkeypatch: MonkeyPatch,
|
|
) -> None:
|
|
"""Explicit messages are already complete and should pass through as-is."""
|
|
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
|
|
client = LLMClient(config)
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
async def _fake_complete(**kwargs: object) -> str:
|
|
captured.update(kwargs)
|
|
return "ok"
|
|
|
|
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
|
|
|
|
messages = [{"role": "user", "content": "from messages"}]
|
|
func = client.get_model_func()
|
|
result = await func("", system_prompt="sys", messages=messages)
|
|
|
|
assert result == "ok"
|
|
assert captured["messages"] == messages
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_client_get_vision_model_func_uses_factory(monkeypatch: MonkeyPatch) -> None:
|
|
"""Vision model func should pass multimodal args into factory."""
|
|
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
|
|
client = LLMClient(config)
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
async def _fake_complete(**kwargs: object) -> str:
|
|
captured.update(kwargs)
|
|
return "ok"
|
|
|
|
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
|
|
|
|
func = client.get_vision_model_func()
|
|
result = await func(
|
|
"hello",
|
|
image_data="abc123",
|
|
messages=[{"role": "user", "content": "hi"}],
|
|
)
|
|
|
|
assert result == "ok"
|
|
assert captured["prompt"] == "hello"
|
|
assert captured["messages"] == [{"role": "user", "content": "hi"}]
|
|
assert captured["image_data"] == "abc123"
|