e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
413 lines
13 KiB
Python
413 lines
13 KiB
Python
"""Tests for TutorBot-style runtime config adapter."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from deeptutor.services.config.provider_runtime import (
|
|
resolve_llm_runtime_config,
|
|
resolve_search_runtime_config,
|
|
)
|
|
|
|
|
|
def _build_catalog(
|
|
*,
|
|
llm_profile: dict | None = None,
|
|
llm_model: dict | None = None,
|
|
search_profile: dict | None = None,
|
|
) -> dict:
|
|
llm_profile = llm_profile or {
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "openai",
|
|
"base_url": "",
|
|
"api_key": "",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [{"id": "llm-m", "name": "m", "model": "gpt-4o-mini"}],
|
|
}
|
|
llm_model = llm_model or llm_profile["models"][0]
|
|
search_profile = search_profile or {
|
|
"id": "search-p",
|
|
"name": "Search",
|
|
"provider": "brave",
|
|
"base_url": "",
|
|
"api_key": "",
|
|
"proxy": "",
|
|
"models": [],
|
|
}
|
|
return {
|
|
"version": 1,
|
|
"services": {
|
|
"llm": {
|
|
"active_profile_id": llm_profile["id"],
|
|
"active_model_id": llm_model["id"],
|
|
"profiles": [llm_profile],
|
|
},
|
|
"embedding": {
|
|
"active_profile_id": None,
|
|
"active_model_id": None,
|
|
"profiles": [],
|
|
},
|
|
"search": {
|
|
"active_profile_id": search_profile["id"],
|
|
"profiles": [search_profile],
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def test_llm_explicit_binding_and_headers() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "dashscope",
|
|
"base_url": "",
|
|
"api_key": "dash-key",
|
|
"api_version": "",
|
|
"extra_headers": {"APP-Code": "abc"},
|
|
"models": [{"id": "llm-m", "name": "q", "model": "qwen-max"}],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.provider_name == "dashscope"
|
|
assert resolved.provider_mode == "standard"
|
|
assert resolved.effective_url == "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
assert resolved.extra_headers == {"APP-Code": "abc"}
|
|
|
|
|
|
def test_llm_api_key_prefix_gateway() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "",
|
|
"base_url": "",
|
|
"api_key": "sk-or-test-key",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [{"id": "llm-m", "name": "m", "model": "gemini-2.5-pro"}],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.provider_name == "openrouter"
|
|
assert resolved.provider_mode == "gateway"
|
|
assert resolved.effective_url == "https://openrouter.ai/api/v1"
|
|
|
|
|
|
def test_llm_api_base_keyword_gateway() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "",
|
|
"base_url": "https://api.aihubmix.com/v1",
|
|
"api_key": "k",
|
|
"api_version": "",
|
|
"extra_headers": {"APP-Code": "x"},
|
|
"models": [{"id": "llm-m", "name": "m", "model": "claude-3-7-sonnet"}],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.provider_name == "aihubmix"
|
|
assert resolved.provider_mode == "gateway"
|
|
assert resolved.effective_url == "https://api.aihubmix.com/v1"
|
|
assert resolved.extra_headers == {"APP-Code": "x"}
|
|
|
|
|
|
def test_llm_local_fallback() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "",
|
|
"base_url": "http://localhost:11434/v1",
|
|
"api_key": "",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [{"id": "llm-m", "name": "m", "model": "llama3.2"}],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.provider_name == "ollama"
|
|
assert resolved.provider_mode == "local"
|
|
assert resolved.api_key == "sk-no-key-required"
|
|
|
|
|
|
def test_llm_minimax_binding_uses_minimaxi_endpoint() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "minimax",
|
|
"base_url": "",
|
|
"api_key": "minimax-key",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [{"id": "llm-m", "name": "m", "model": "MiniMax-M3"}],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.provider_name == "minimax"
|
|
assert resolved.provider_mode == "standard"
|
|
assert resolved.effective_url == "https://api.minimaxi.com/v1"
|
|
|
|
|
|
def test_llm_minimax_anthropic_binding_uses_anthropic_endpoint() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "minimax_anthropic",
|
|
"base_url": "",
|
|
"api_key": "minimax-key",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [{"id": "llm-m", "name": "c", "model": "claude-sonnet-4-20250514"}],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.provider_name == "minimax_anthropic"
|
|
assert resolved.provider_mode == "standard"
|
|
assert resolved.effective_url == "https://api.minimaxi.com/anthropic"
|
|
|
|
|
|
def test_llm_custom_anthropic_binding_stays_direct() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "custom_anthropic",
|
|
"base_url": "https://claude-proxy.example/v1/messages",
|
|
"api_key": "anthropic-key",
|
|
"api_version": "",
|
|
"extra_headers": {"x-tenant": "lab"},
|
|
"models": [{"id": "llm-m", "name": "c", "model": "claude-sonnet-4-20250514"}],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.provider_name == "custom_anthropic"
|
|
assert resolved.provider_mode == "direct"
|
|
assert resolved.binding == "custom_anthropic"
|
|
assert resolved.effective_url == "https://claude-proxy.example/v1/messages"
|
|
assert resolved.extra_headers == {"x-tenant": "lab"}
|
|
|
|
|
|
def test_llm_lm_studio_alias_resolves_to_local_provider() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "lm-studio",
|
|
"base_url": "",
|
|
"api_key": "",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [{"id": "llm-m", "name": "m", "model": "llama-3.2"}],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.provider_name == "lm_studio"
|
|
assert resolved.provider_mode == "local"
|
|
assert resolved.effective_url == "http://localhost:1234/v1"
|
|
assert resolved.api_key == "sk-no-key-required"
|
|
|
|
|
|
def test_llm_context_window_passes_through_from_catalog() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "openai",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"api_key": "sk-test",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [
|
|
{
|
|
"id": "llm-m",
|
|
"name": "GPT 4o mini",
|
|
"model": "gpt-4o-mini",
|
|
"context_window": 128000,
|
|
}
|
|
],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.context_window == 128000
|
|
|
|
|
|
def test_llm_selection_overrides_active_model_without_mutating_catalog() -> None:
|
|
profile_a = {
|
|
"id": "p-a",
|
|
"name": "OpenRouter",
|
|
"binding": "openrouter",
|
|
"base_url": "https://openrouter.ai/api/v1",
|
|
"api_key": "sk-or-test",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [
|
|
{
|
|
"id": "m-a",
|
|
"name": "Gemini",
|
|
"model": "google/gemini-3-flash-preview",
|
|
}
|
|
],
|
|
}
|
|
profile_b = {
|
|
"id": "p-b",
|
|
"name": "Local",
|
|
"binding": "ollama",
|
|
"base_url": "http://localhost:11434/v1",
|
|
"api_key": "",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [{"id": "m-b", "name": "Llama", "model": "llama3.2"}],
|
|
}
|
|
catalog = _build_catalog(llm_profile=profile_a, llm_model=profile_a["models"][0])
|
|
catalog["services"]["llm"]["profiles"].append(profile_b)
|
|
|
|
resolved = resolve_llm_runtime_config(
|
|
catalog=catalog,
|
|
llm_selection={"profile_id": "p-b", "model_id": "m-b"},
|
|
)
|
|
|
|
assert resolved.model == "llama3.2"
|
|
assert resolved.provider_name == "ollama"
|
|
assert resolved.provider_mode == "local"
|
|
assert catalog["services"]["llm"]["active_profile_id"] == "p-a"
|
|
assert catalog["services"]["llm"]["active_model_id"] == "m-a"
|
|
|
|
|
|
def test_llm_reasoning_effort_resolves_from_catalog() -> None:
|
|
catalog = _build_catalog(
|
|
llm_profile={
|
|
"id": "llm-p",
|
|
"name": "LLM",
|
|
"binding": "openai",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"api_key": "sk-test",
|
|
"api_version": "",
|
|
"extra_headers": {},
|
|
"models": [
|
|
{
|
|
"id": "llm-m",
|
|
"name": "GPT 4o mini",
|
|
"model": "gpt-4o-mini",
|
|
"reasoning_effort": "high",
|
|
}
|
|
],
|
|
}
|
|
)
|
|
resolved = resolve_llm_runtime_config(catalog=catalog)
|
|
assert resolved.reasoning_effort == "high"
|
|
|
|
|
|
def test_search_fallback_to_duckduckgo_without_key() -> None:
|
|
catalog = _build_catalog(
|
|
search_profile={
|
|
"id": "search-p",
|
|
"name": "Search",
|
|
"provider": "brave",
|
|
"base_url": "",
|
|
"api_key": "",
|
|
"proxy": "http://127.0.0.1:7890",
|
|
"models": [],
|
|
}
|
|
)
|
|
resolved = resolve_search_runtime_config(catalog=catalog)
|
|
assert resolved.provider == "duckduckgo"
|
|
assert resolved.requested_provider == "brave"
|
|
assert resolved.fallback_reason is not None
|
|
assert resolved.proxy == "http://127.0.0.1:7890"
|
|
|
|
|
|
def test_search_none_disables_runtime_provider() -> None:
|
|
catalog = _build_catalog(
|
|
search_profile={
|
|
"id": "search-p",
|
|
"name": "Search",
|
|
"provider": "none",
|
|
"base_url": "",
|
|
"api_key": "",
|
|
"proxy": "",
|
|
"models": [],
|
|
}
|
|
)
|
|
resolved = resolve_search_runtime_config(catalog=catalog)
|
|
assert resolved.provider == "none"
|
|
assert resolved.requested_provider == "none"
|
|
assert resolved.status == "ok"
|
|
|
|
|
|
def test_search_marks_deprecated_provider() -> None:
|
|
catalog = _build_catalog(
|
|
search_profile={
|
|
"id": "search-p",
|
|
"name": "Search",
|
|
"provider": "exa",
|
|
"base_url": "",
|
|
"api_key": "k",
|
|
"proxy": "",
|
|
"models": [],
|
|
}
|
|
)
|
|
resolved = resolve_search_runtime_config(catalog=catalog)
|
|
assert resolved.unsupported_provider is True
|
|
assert resolved.deprecated_provider is True
|
|
assert resolved.provider == "exa"
|
|
|
|
|
|
def test_search_perplexity_missing_credentials() -> None:
|
|
catalog = _build_catalog(
|
|
search_profile={
|
|
"id": "search-p",
|
|
"name": "Search",
|
|
"provider": "perplexity",
|
|
"base_url": "",
|
|
"api_key": "",
|
|
"proxy": "",
|
|
"models": [],
|
|
}
|
|
)
|
|
resolved = resolve_search_runtime_config(catalog=catalog)
|
|
assert resolved.provider == "perplexity"
|
|
assert resolved.unsupported_provider is False
|
|
assert resolved.deprecated_provider is False
|
|
assert resolved.missing_credentials is True
|
|
|
|
|
|
def test_search_serper_missing_credentials() -> None:
|
|
catalog = _build_catalog(
|
|
search_profile={
|
|
"id": "search-p",
|
|
"name": "Search",
|
|
"provider": "serper",
|
|
"base_url": "",
|
|
"api_key": "",
|
|
"proxy": "",
|
|
"models": [],
|
|
}
|
|
)
|
|
resolved = resolve_search_runtime_config(catalog=catalog)
|
|
assert resolved.provider == "serper"
|
|
assert resolved.unsupported_provider is False
|
|
assert resolved.deprecated_provider is False
|
|
assert resolved.missing_credentials is True
|
|
|
|
|
|
def test_search_searxng_without_url_fallback() -> None:
|
|
catalog = _build_catalog(
|
|
search_profile={
|
|
"id": "search-p",
|
|
"name": "Search",
|
|
"provider": "searxng",
|
|
"base_url": "",
|
|
"api_key": "",
|
|
"proxy": "",
|
|
"models": [],
|
|
}
|
|
)
|
|
resolved = resolve_search_runtime_config(catalog=catalog)
|
|
assert resolved.provider == "duckduckgo"
|
|
assert resolved.fallback_reason is not None
|