test_llm.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """Tests for the LLM client and chat session (no network)."""
  2. import pytest
  3. from stt_daemon.assistant.llm import ChatSession, LlmClient, LlmError
  4. from stt_daemon.config import LlmConfig
  5. def _make_client(responder, cfg=None):
  6. cfg = cfg or LlmConfig()
  7. calls: list[dict] = []
  8. def opener(payload, timeout):
  9. calls.append(payload)
  10. return responder(payload)
  11. return LlmClient(cfg, opener=opener), calls
  12. def _reply(content="", reasoning=None, tool_calls=None, finish="stop"):
  13. message = {"role": "assistant", "content": content}
  14. if reasoning is not None:
  15. message["reasoning_content"] = reasoning
  16. if tool_calls:
  17. message["tool_calls"] = tool_calls
  18. return {"choices": [{"message": message, "finish_reason": finish}]}
  19. def test_reasoning_content_is_ignored():
  20. client, _ = _make_client(
  21. lambda _p: _reply(content="It is noon.", reasoning="thinking hard about it")
  22. )
  23. response = client.chat([{"role": "user", "content": "hi"}])
  24. assert response.content == "It is noon."
  25. assert "thinking hard" not in response.content
  26. def test_cache_prompt_and_endpoint_are_sent():
  27. client, calls = _make_client(lambda _p: _reply(content="ok"))
  28. client.chat([{"role": "user", "content": "hi"}])
  29. assert calls[0]["body"]["cache_prompt"] is True
  30. assert calls[0]["body"]["stream"] is False
  31. assert calls[0]["__url__"] == LlmConfig().endpoint
  32. def test_tools_sent_only_when_provided():
  33. client, calls = _make_client(lambda _p: _reply(content="ok"))
  34. client.chat([{"role": "user", "content": "hi"}])
  35. assert "tools" not in calls[0]["body"]
  36. client.chat(
  37. [{"role": "user", "content": "hi"}],
  38. tools=[{"type": "function", "function": {"name": "get_datetime"}}],
  39. )
  40. assert calls[1]["body"]["tools"][0]["function"]["name"] == "get_datetime"
  41. assert calls[1]["body"]["tool_choice"] == "auto"
  42. def test_tool_calls_are_parsed():
  43. client, _ = _make_client(
  44. lambda _p: _reply(
  45. tool_calls=[
  46. {
  47. "id": "1",
  48. "type": "function",
  49. "function": {"name": "get_datetime", "arguments": "{}"},
  50. }
  51. ],
  52. finish="tool_calls",
  53. )
  54. )
  55. response = client.chat([{"role": "user", "content": "time?"}], tools=[{"type": "function"}])
  56. assert len(response.tool_calls) == 1
  57. assert response.tool_calls[0].name == "get_datetime"
  58. assert response.finish_reason == "tool_calls"
  59. def test_missing_choices_raises():
  60. client, _ = _make_client(lambda _p: {"choices": []})
  61. with pytest.raises(LlmError):
  62. client.chat([{"role": "user", "content": "hi"}])
  63. def test_chat_session_appends_and_keeps_prefix():
  64. client, calls = _make_client(lambda _p: _reply(content="hello"))
  65. session = ChatSession(LlmConfig(), client)
  66. assert session.send("hi") == "hello"
  67. session.send("again")
  68. assert [m["role"] for m in session.history] == [
  69. "user",
  70. "assistant",
  71. "user",
  72. "assistant",
  73. ]
  74. first = calls[0]["body"]["messages"]
  75. second = calls[1]["body"]["messages"]
  76. assert second[: len(first)] == first # append-only, prefix preserved
  77. assert second[0]["content"] == LlmConfig().system_prompt_chat
  78. def test_chat_session_trims_in_blocks_and_keeps_system_prompt():
  79. cfg = LlmConfig(max_prompt_tokens=50, trim_block_turns=1)
  80. client, calls = _make_client(lambda _p: _reply(content="ok"))
  81. session = ChatSession(cfg, client)
  82. for _ in range(10):
  83. session.send("x" * 40)
  84. assert len(session.history) <= 6
  85. assert calls[-1]["body"]["messages"][0]["content"] == cfg.system_prompt_chat
  86. def test_chat_session_reset_forgets_history():
  87. client, _ = _make_client(lambda _p: _reply(content="ok"))
  88. session = ChatSession(LlmConfig(), client)
  89. session.send("hi")
  90. session.reset()
  91. assert session.history == []