from didactopus.config import ModelProviderConfig from didactopus.model_provider import ModelProvider from didactopus.role_prompts import evaluator_system_prompt, mentor_system_prompt, system_prompt_for_role def test_stub_provider_includes_role_marker() -> None: provider = ModelProvider(ModelProviderConfig()) response = provider.generate("Explain entropy simply.", role="mentor") assert response.provider == "stub" assert "[mentor]" in response.text def test_rolemesh_provider_uses_role_mapping() -> None: config = ModelProviderConfig.model_validate( { "provider": "rolemesh", "rolemesh": { "base_url": "http://127.0.0.1:8000", "api_key": "demo", "default_model": "planner", "role_to_model": {"mentor": "planner", "practice": "writer"}, }, } ) provider = ModelProvider(config) def fake_chat(payload: dict) -> dict: assert payload["model"] == "writer" assert payload["messages"][0]["role"] == "system" return {"choices": [{"message": {"content": "Practice task response"}}]} provider._rolemesh_chat_completion = fake_chat # type: ignore[method-assign] response = provider.generate( "Generate a practice task.", role="practice", system_prompt="System prompt", ) assert response.provider == "rolemesh" assert response.model_name == "writer" assert response.text == "Practice task response" def test_rolemesh_provider_emits_pending_notice() -> None: config = ModelProviderConfig.model_validate( { "provider": "rolemesh", "rolemesh": { "base_url": "http://127.0.0.1:8000", "api_key": "demo", "default_model": "planner", "role_to_model": {"evaluator": "reviewer"}, }, } ) provider = ModelProvider(config) seen: list[str] = [] def fake_chat(payload: dict) -> dict: return {"choices": [{"message": {"content": "Evaluation response"}}]} provider._rolemesh_chat_completion = fake_chat # type: ignore[method-assign] response = provider.generate( "Evaluate a learner answer.", role="evaluator", status_callback=seen.append, ) assert response.text == "Evaluation response" assert seen == ["Didactopus is evaluating the work before replying. Model: reviewer."] def test_ollama_provider_uses_role_mapping() -> None: config = ModelProviderConfig.model_validate( { "provider": "ollama", "ollama": { "base_url": "http://127.0.0.1:11434/v1", "api_key": "ollama", "default_model": "llama3.2:3b", "role_to_model": {"mentor": "llama3.2:3b", "practice": "qwen2.5:3b"}, }, } ) provider = ModelProvider(config) def fake_chat(*, base_url: str, api_key: str, timeout_seconds: float, payload: dict, auth_scheme: str) -> dict: assert base_url == "http://127.0.0.1:11434/v1" assert api_key == "ollama" assert payload["model"] == "qwen2.5:3b" assert auth_scheme == "bearer" return {"choices": [{"message": {"content": "Ollama practice response"}}]} provider._chat_completion_request = fake_chat # type: ignore[method-assign] response = provider.generate( "Generate a practice task.", role="practice", system_prompt="System prompt", ) assert response.provider == "ollama" assert response.model_name == "qwen2.5:3b" assert response.text == "Ollama practice response" def test_openai_compatible_provider_uses_bearer_auth() -> None: config = ModelProviderConfig.model_validate( { "provider": "openai_compatible", "openai_compatible": { "base_url": "https://api.openai.com/v1", "api_key": "demo-key", "default_model": "gpt-4.1-mini", "role_to_model": {"mentor": "gpt-4.1-mini"}, "auth_scheme": "bearer", }, } ) provider = ModelProvider(config) def fake_chat(*, base_url: str, api_key: str, timeout_seconds: float, payload: dict, auth_scheme: str) -> dict: assert base_url == "https://api.openai.com/v1" assert api_key == "demo-key" assert payload["model"] == "gpt-4.1-mini" assert auth_scheme == "bearer" return {"choices": [{"message": {"content": "Hosted mentor response"}}]} provider._chat_completion_request = fake_chat # type: ignore[method-assign] response = provider.generate( "Orient the learner.", role="mentor", system_prompt="System prompt", ) assert response.provider == "openai_compatible" assert response.model_name == "gpt-4.1-mini" assert response.text == "Hosted mentor response" def test_evaluator_prompt_requires_checking_existing_caveats() -> None: prompt = evaluator_system_prompt().lower() assert "before saying something is missing" in prompt assert "quote or paraphrase" in prompt assert "do not invent omissions" in prompt def test_mentor_prompt_requires_acknowledging_existing_caveats() -> None: prompt = mentor_system_prompt().lower() assert "acknowledge what the learner already did correctly" in prompt assert "do not claim a caveat" in prompt def test_system_prompt_for_role_covers_defined_roles() -> None: assert "mentor mode" in system_prompt_for_role("mentor").lower() assert "practice-design mode" in system_prompt_for_role("practice").lower()