from __future__ import annotations import json import unittest from typing import Any import requests from models.model_catalog import load_model_catalog from models.openai_compatible_service import ( OpenAICompatibleConfig, OpenAICompatibleService, ) def _json_response(payload: dict[str, Any], status_code: int = 200) -> requests.Response: response = requests.Response() response.status_code = status_code response._content = json.dumps(payload).encode("utf-8") return response class CapturingPost: def __init__(self) -> None: self.payload: dict[str, Any] = {} self.url = "" def __call__(self, url: str, **kwargs: Any) -> requests.Response: self.url = url self.payload = dict(kwargs["json"]) return _json_response({"choices": [{"message": {"content": "local answer"}}]}) class OpenAICompatibleServiceTest(unittest.TestCase): def setUp(self) -> None: self.model = load_model_catalog("config/models.yaml")["minicpm5_1b"] def test_status_reports_reachable_server(self) -> None: def get_models(url: str, **kwargs: Any) -> requests.Response: self.assertEqual(url, "http://local.test/v1/models") self.assertEqual(kwargs["timeout"], 2) return _json_response({"data": [{"id": "loaded-model"}]}) status = OpenAICompatibleService.status("http://local.test", get_models) self.assertTrue(status.available) self.assertEqual(status.name, "openai-compatible") def test_status_reports_unreachable_server(self) -> None: def get_models(url: str, **kwargs: Any) -> requests.Response: del url, kwargs raise requests.ConnectionError("not listening") status = OpenAICompatibleService.status("http://local.test", get_models) self.assertFalse(status.available) self.assertIn("not reachable", status.detail) def test_chat_posts_openai_compatible_payload_with_model_override(self) -> None: def get_models(url: str, **kwargs: Any) -> requests.Response: del url, kwargs return _json_response({"data": [{"id": "lm-studio-model"}]}) post_chat = CapturingPost() service = OpenAICompatibleService( self.model, OpenAICompatibleConfig( base_url="http://local.test/", model_name="lm-studio-model", api_key="local-key", ), get_func=get_models, post_func=post_chat, ) answer = service.chat("be concise", "hello") self.assertEqual(answer, "local answer") self.assertEqual(post_chat.url, "http://local.test/v1/chat/completions") self.assertEqual(post_chat.payload["model"], "lm-studio-model") self.assertEqual(post_chat.payload["messages"][0]["role"], "system") self.assertEqual(post_chat.payload["messages"][1]["content"], "hello") def test_chat_returns_unavailable_message_without_posting(self) -> None: def get_models(url: str, **kwargs: Any) -> requests.Response: del url, kwargs raise requests.ConnectionError("server down") def post_chat(url: str, **kwargs: Any) -> requests.Response: del url, kwargs raise AssertionError("chat request should not be sent when status fails") service = OpenAICompatibleService( self.model, OpenAICompatibleConfig(base_url="http://local.test"), get_func=get_models, post_func=post_chat, ) answer = service.chat("system", "prompt") self.assertIn("[OpenAI-compatible backend unavailable]", answer) self.assertIn("server down", answer) def test_chat_returns_request_failure_message(self) -> None: def get_models(url: str, **kwargs: Any) -> requests.Response: del url, kwargs return _json_response({"data": [{"id": "loaded-model"}]}) def post_chat(url: str, **kwargs: Any) -> requests.Response: del url, kwargs raise requests.Timeout("slow local model") service = OpenAICompatibleService( self.model, OpenAICompatibleConfig(base_url="http://local.test"), get_func=get_models, post_func=post_chat, ) answer = service.chat("system", "prompt") self.assertIn("[OpenAI-compatible request failed]", answer) self.assertIn("slow local model", answer) if __name__ == "__main__": unittest.main()