245 lines
7.4 KiB
Python
245 lines
7.4 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Tests for the ``lmcache query`` CLI command."""
|
|
|
|
# Standard
|
|
from unittest.mock import MagicMock, patch
|
|
import argparse
|
|
|
|
# Third Party
|
|
import pytest
|
|
|
|
# First Party
|
|
from lmcache.cli.commands.query import QueryCommand
|
|
|
|
|
|
def _engine_metric_map(
|
|
model_id: str = "facebook/opt-125m",
|
|
) -> dict[str, tuple[str, object]]:
|
|
"""Metric-map portion of :meth:`lmcache.cli.request.Request.send_request`."""
|
|
return {
|
|
"model": ("Model", model_id),
|
|
"prompt_tokens": ("Input tokens", 10),
|
|
"output_tokens": ("Output tokens", 5),
|
|
"ttft_ms": ("TTFT (ms)", 1.0),
|
|
"tpot_ms_per_token": ("TPOT (ms/token)", 2.0),
|
|
"total_latency_ms": ("Total latency (ms)", 100.0),
|
|
"throughput_tokens_per_s": ("Throughput (tokens/s)", 50.0),
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def cmd() -> QueryCommand:
|
|
return QueryCommand()
|
|
|
|
|
|
@pytest.fixture
|
|
def parser(cmd: QueryCommand) -> argparse.ArgumentParser:
|
|
"""An :class:`~argparse.ArgumentParser` with ``QueryCommand`` registered."""
|
|
p = argparse.ArgumentParser()
|
|
sub = p.add_subparsers(dest="command")
|
|
cmd.register(sub)
|
|
return p
|
|
|
|
|
|
class TestQueryCommandMetadata:
|
|
def test_name(self, cmd: QueryCommand) -> None:
|
|
assert cmd.name() == "query"
|
|
|
|
def test_help(self, cmd: QueryCommand) -> None:
|
|
assert "inference" in cmd.help().lower()
|
|
|
|
|
|
class TestQueryCommandArguments:
|
|
def test_registers_subcommand(self, parser: argparse.ArgumentParser) -> None:
|
|
"""The ``query engine`` subcommand should be parseable."""
|
|
args = parser.parse_args(
|
|
[
|
|
"query",
|
|
"engine",
|
|
"--url",
|
|
"http://localhost:8000/v1",
|
|
"--prompt",
|
|
"hello",
|
|
],
|
|
)
|
|
assert hasattr(args, "func")
|
|
|
|
def test_engine_args_registered(self, parser: argparse.ArgumentParser) -> None:
|
|
args = parser.parse_args(
|
|
[
|
|
"query",
|
|
"engine",
|
|
"--url",
|
|
"http://host:9/v1",
|
|
"--prompt",
|
|
"{ffmpeg} test",
|
|
"--model",
|
|
"m",
|
|
"--max-tokens",
|
|
"64",
|
|
"--timeout",
|
|
"5",
|
|
"--documents",
|
|
"a=/tmp/x",
|
|
"--completions",
|
|
"--chat-first",
|
|
"--format",
|
|
"json",
|
|
"--output",
|
|
"/tmp/out",
|
|
],
|
|
)
|
|
assert args.url == "http://host:9/v1"
|
|
assert args.prompt == "{ffmpeg} test"
|
|
assert args.model == "m"
|
|
assert args.max_tokens == 64
|
|
assert args.timeout == 5.0
|
|
assert args.documents == ["a=/tmp/x"]
|
|
assert args.completions is True
|
|
assert args.chat_first is True
|
|
assert args.format == "json"
|
|
assert args.output == "/tmp/out"
|
|
|
|
def test_default_values(self, parser: argparse.ArgumentParser) -> None:
|
|
"""Required args only — everything else should get defaults."""
|
|
args = parser.parse_args(
|
|
[
|
|
"query",
|
|
"engine",
|
|
"--url",
|
|
"http://localhost:8000/v1",
|
|
"--prompt",
|
|
"hi",
|
|
],
|
|
)
|
|
assert args.model is None
|
|
assert args.max_tokens == 128
|
|
assert args.timeout == 30.0
|
|
assert args.documents == []
|
|
assert args.completions is False
|
|
assert args.chat_first is False
|
|
assert args.format is None
|
|
assert args.output is None
|
|
|
|
|
|
class TestQueryCommandExecute:
|
|
def test_func_bound_to_execute(
|
|
self, cmd: QueryCommand, parser: argparse.ArgumentParser
|
|
) -> None:
|
|
"""``parse_args`` should bind ``func`` to the subcommand's execute."""
|
|
args = parser.parse_args(
|
|
[
|
|
"query",
|
|
"engine",
|
|
"--url",
|
|
"http://localhost:8000/v1",
|
|
"--prompt",
|
|
"hello",
|
|
"--model",
|
|
"m",
|
|
],
|
|
)
|
|
# func is bound to the discovered EngineCommand's execute
|
|
assert callable(args.func)
|
|
assert args.query_target == "engine"
|
|
|
|
@patch("lmcache.cli.commands.query.engine_command.Request")
|
|
def test_execute_calls_request_send_request(
|
|
self,
|
|
mock_request_cls: MagicMock,
|
|
cmd: QueryCommand,
|
|
parser: argparse.ArgumentParser,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
"""query_engine should call Request.send_request with the expanded prompt."""
|
|
mock_instance = MagicMock()
|
|
mock_instance.send_request.return_value = ("Paris.", _engine_metric_map())
|
|
mock_request_cls.return_value = mock_instance
|
|
|
|
args = parser.parse_args(
|
|
[
|
|
"query",
|
|
"engine",
|
|
"--url",
|
|
"http://localhost:8000/v1",
|
|
"--prompt",
|
|
"hello",
|
|
"--model",
|
|
"facebook/opt-125m",
|
|
],
|
|
)
|
|
cmd.execute(args)
|
|
|
|
mock_request_cls.assert_called_once_with(
|
|
base="http://localhost:8000/v1",
|
|
model="facebook/opt-125m",
|
|
max_tokens=128,
|
|
timeout=30.0,
|
|
completions_only=False,
|
|
chat_first=False,
|
|
)
|
|
mock_instance.send_request.assert_called_once_with("hello")
|
|
|
|
out = capsys.readouterr().out
|
|
assert "Query Engine" in out
|
|
assert "facebook/opt-125m" in out
|
|
assert "Input tokens" in out
|
|
assert "Prompt tokens" not in out
|
|
assert "Answer" in out
|
|
assert "Paris." in out
|
|
|
|
@patch("lmcache.cli.commands.query.engine_command.Request")
|
|
def test_execute_uses_engine_model_when_cli_model_omitted(
|
|
self,
|
|
mock_request_cls: MagicMock,
|
|
cmd: QueryCommand,
|
|
parser: argparse.ArgumentParser,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
"""With no ``--model``, the report uses the model id from engine stats."""
|
|
mock_instance = MagicMock()
|
|
mock_instance.send_request.return_value = (
|
|
"answer",
|
|
_engine_metric_map(model_id="listed-model"),
|
|
)
|
|
mock_request_cls.return_value = mock_instance
|
|
|
|
args = parser.parse_args(
|
|
[
|
|
"query",
|
|
"engine",
|
|
"--url",
|
|
"http://localhost:8000/v1",
|
|
"--prompt",
|
|
"x",
|
|
],
|
|
)
|
|
cmd.execute(args)
|
|
assert "listed-model" in capsys.readouterr().out
|
|
|
|
def test_execute_invalid_prompt_exits(
|
|
self,
|
|
cmd: QueryCommand,
|
|
parser: argparse.ArgumentParser,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
"""Unknown ``{placeholder}`` without ``--documents`` raises and exits."""
|
|
args = parser.parse_args(
|
|
[
|
|
"query",
|
|
"engine",
|
|
"--url",
|
|
"http://localhost:8000/v1",
|
|
"--prompt",
|
|
"{unknown_corpus}",
|
|
"--model",
|
|
"m",
|
|
],
|
|
)
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
cmd.execute(args)
|
|
assert exc_info.value.code == 1
|
|
err = capsys.readouterr().err
|
|
assert "Unknown documents" in err
|
|
assert "unknown_corpus" in err
|