test_adapter.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import json
  2. import importlib.util
  3. from pathlib import Path
  4. import unittest
  5. MODULE_PATH = (
  6. Path(__file__).resolve().parent.parent / "litellm_lmstudio_adapter.py"
  7. )
  8. if MODULE_PATH.exists():
  9. spec = importlib.util.spec_from_file_location("litellm_lmstudio_adapter", MODULE_PATH)
  10. module = importlib.util.module_from_spec(spec)
  11. assert spec is not None and spec.loader is not None
  12. spec.loader.exec_module(module)
  13. adapter = module
  14. else: # pragma: no cover
  15. adapter = None
  16. class LiteLLMLMStudioAdapterTests(unittest.TestCase):
  17. def setUp(self):
  18. if adapter is None:
  19. self.fail("litellm_lmstudio_adapter.app is not available")
  20. def test_build_chat_native_payload_maps_reasoning(self):
  21. payload = {
  22. "model": "qwen/qwen3.6-35b-a3b",
  23. "messages": [
  24. {"role": "system", "content": "be precise"},
  25. {"role": "user", "content": "hello"},
  26. {"role": "assistant", "content": "hi"},
  27. {"role": "user", "content": "continue"},
  28. ],
  29. "reasoning": "off",
  30. "max_tokens": 64,
  31. "temperature": 0.2,
  32. }
  33. native_payload = adapter.build_chat_native_payload(payload)
  34. self.assertEqual(native_payload["model"], "qwen/qwen3.6-35b-a3b")
  35. self.assertEqual(
  36. native_payload["input"],
  37. "User: hello\n\nAssistant: hi\n\nUser: continue",
  38. )
  39. self.assertEqual(native_payload["system_prompt"], "be precise")
  40. self.assertEqual(native_payload["reasoning"], "off")
  41. self.assertEqual(native_payload["max_output_tokens"], 64)
  42. def test_translate_chat_stream_chunk_emits_reasoning_delta(self):
  43. event = {
  44. "type": "reasoning.delta",
  45. "content": "step 1",
  46. "model": "qwen/qwen3.6-35b-a3b",
  47. }
  48. chunk = adapter.translate_chat_stream_event(
  49. event=event,
  50. model="qwen/qwen3.6-35b-a3b",
  51. chunk_id="chatcmpl-test",
  52. )
  53. self.assertIsNotNone(chunk)
  54. parsed = json.loads(chunk.removeprefix("data: ").strip())
  55. self.assertEqual(parsed["object"], "chat.completion.chunk")
  56. self.assertEqual(parsed["choices"][0]["delta"]["reasoning_content"], "step 1")
  57. def test_translate_chat_stream_chunk_emits_content_delta(self):
  58. event = {
  59. "type": "message.delta",
  60. "content": "9193",
  61. "model": "qwen/qwen3.6-35b-a3b",
  62. }
  63. chunk = adapter.translate_chat_stream_event(
  64. event=event,
  65. model="qwen/qwen3.6-35b-a3b",
  66. chunk_id="chatcmpl-test",
  67. )
  68. parsed = json.loads(chunk.removeprefix("data: ").strip())
  69. self.assertEqual(parsed["choices"][0]["delta"]["content"], "9193")
  70. def test_translate_chat_stream_chunk_ignores_non_content_events(self):
  71. event = {
  72. "type": "prompt_processing.start",
  73. }
  74. chunk = adapter.translate_chat_stream_event(
  75. event=event,
  76. model="qwen/qwen3.6-35b-a3b",
  77. chunk_id="chatcmpl-test",
  78. )
  79. self.assertIsNone(chunk)
  80. def test_translate_responses_stream_event_emits_output_text_delta(self):
  81. event = {
  82. "type": "message.delta",
  83. "content": "9193",
  84. "model": "qwen/qwen3.6-35b-a3b",
  85. }
  86. frames = adapter.translate_responses_stream_event(
  87. event=event,
  88. model="qwen/qwen3.6-35b-a3b",
  89. response_id="resp_test",
  90. )
  91. self.assertTrue(frames)
  92. parsed = json.loads(frames[0].removeprefix("data: ").strip())
  93. self.assertEqual(parsed["type"], "response.output_text.delta")
  94. self.assertEqual(parsed["delta"], "9193")
  95. def test_parse_sse_event_blocks_extracts_event_and_data(self):
  96. raw = (
  97. "event: content_block.delta\n"
  98. "data: {\"type\":\"message\",\"content\":\"9193\"}\n\n"
  99. "event: done\n"
  100. "data: [DONE]\n\n"
  101. )
  102. blocks = list(adapter.parse_sse_event_blocks(raw))
  103. self.assertEqual(len(blocks), 2)
  104. self.assertEqual(blocks[0]["event"], "content_block.delta")
  105. self.assertEqual(blocks[0]["data"]["content"], "9193")
  106. self.assertEqual(blocks[1]["event"], "done")
  107. self.assertEqual(blocks[1]["data_raw"], "[DONE]")
  108. def test_build_stub_error_uses_openai_shape(self):
  109. response = adapter.build_stub_error("audio.transcriptions")
  110. self.assertEqual(response.status_code, 501)
  111. payload = json.loads(response.body.decode("utf-8"))
  112. self.assertEqual(payload["error"]["type"], "not_implemented")
  113. self.assertIn("audio.transcriptions", payload["error"]["message"])
  114. if __name__ == "__main__":
  115. unittest.main()