Files

1 line
9.0 KiB
JSON

{"repo": "17625398/opendeepbot", "files": {"tests/services/config/test_provider_runtime.py": {"0c06ea9127735717357a8d5324022f0d1aa07d62": {"t": 1785768989.4239872, "n": 8239, "h": "bff0806780f9b833", "c": "\"\"\"Tests for TutorBot-style runtime config adapter.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\n\nfrom deeptutor.services.config.env_store import EnvStore\nfrom deeptutor.services.config.provider_runtime import (\n resolve_llm_runtime_config,\n resolve_search_runtime_config,\n)\n\n\ndef _build_catalog(\n *,\n llm_profile: dict | None = None,\n llm_model: dict | None = None,\n search_profile: dict | None = None,\n) -> dict:\n llm_profile = llm_profile or {\n \"id\": \"llm-p\",\n \"name\": \"LLM\",\n \"binding\": \"openai\",\n \"base_url\": \"\",\n \"api_key\": \"\",\n \"api_version\": \"\",\n \"extra_headers\": {},\n \"models\": [{\"id\": \"llm-m\", \"name\": \"m\", \"model\": \"gpt-4o-mini\"}],\n }\n llm_model = llm_model or llm_profile[\"models\"][0]\n search_profile = search_profile or {\n \"id\": \"search-p\",\n \"name\": \"Search\",\n \"provider\": \"brave\",\n \"base_url\": \"\",\n \"api_key\": \"\",\n \"proxy\": \"\",\n \"models\": [],\n }\n return {\n \"version\": 1,\n \"services\": {\n \"llm\": {\n \"active_profile_id\": llm_profile[\"id\"],\n \"active_model_id\": llm_model[\"id\"],\n \"profiles\": [llm_profile],\n },\n \"embedding\": {\n \"active_profile_id\": None,\n \"active_model_id\": None,\n \"profiles\": [],\n },\n \"search\": {\n \"active_profile_id\": search_profile[\"id\"],\n \"profiles\": [search_profile],\n },\n },\n }\n\n\ndef _empty_env(tmp_path: Path) -> EnvStore:\n env_path = tmp_path / \".env\"\n env_path.write_text(\n \"\\n\".join(\n [\n \"LLM_BINDING=\",\n \"LLM_MODEL=\",\n \"LLM_API_KEY=\",\n \"LLM_HOST=\",\n \"LLM_API_VERSION=\",\n \"SEARCH_PROVIDER=\",\n \"SEARCH_API_KEY=\",\n \"SEARCH_BASE_URL=\",\n \"SEARCH_PROXY=\",\n \"BRAVE_API_KEY=\",\n \"TAVILY_API_KEY=\",\n \"JINA_API_KEY=\",\n \"SEARXNG_BASE_URL=\",\n \"PERPLEXITY_API_KEY=\",\n \"SERPER_API_KEY=\",\n ]\n )\n + \"\\n\",\n encoding=\"utf-8\",\n )\n return EnvStore(path=env_path)\n\n\ndef test_llm_explicit_binding_and_headers(tmp_path: Path) -> None:\n catalog = _build_catalog(\n llm_profile={\n \"id\": \"llm-p\",\n \"name\": \"LLM\",\n \"binding\": \"dashscope\",\n \"base_url\": \"\",\n \"api_key\": \"dash-key\",\n \"api_version\": \"\",\n \"extra_headers\": {\"APP-Code\": \"abc\"},\n \"models\": [{\"id\": \"llm-m\", \"name\": \"q\", \"model\": \"qwen-max\"}],\n }\n )\n resolved = resolve_llm_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.provider_name == \"dashscope\"\n assert resolved.provider_mode == \"standard\"\n assert resolved.effective_url == \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\n assert resolved.extra_headers == {\"APP-Code\": \"abc\"}\n\n\ndef test_llm_api_key_prefix_gateway(tmp_path: Path) -> None:\n catalog = _build_catalog(\n llm_profile={\n \"id\": \"llm-p\",\n \"name\": \"LLM\",\n \"binding\": \"\",\n \"base_url\": \"\",\n \"api_key\": \"sk-or-test-key\",\n \"api_version\": \"\",\n \"extra_headers\": {},\n \"models\": [{\"id\": \"llm-m\", \"name\": \"m\", \"model\": \"gemini-2.5-pro\"}],\n }\n )\n resolved = resolve_llm_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.provider_name == \"openrouter\"\n assert resolved.provider_mode == \"gateway\"\n assert resolved.effective_url == \"https://openrouter.ai/api/v1\"\n\n\ndef test_llm_api_base_keyword_gateway(tmp_path: Path) -> None:\n catalog = _build_catalog(\n llm_profile={\n \"id\": \"llm-p\",\n \"name\": \"LLM\",\n \"binding\": \"\",\n \"base_url\": \"https://api.aihubmix.com/v1\",\n \"api_key\": \"k\",\n \"api_version\": \"\",\n \"extra_headers\": {\"APP-Code\": \"x\"},\n \"models\": [{\"id\": \"llm-m\", \"name\": \"m\", \"model\": \"claude-3-7-sonnet\"}],\n }\n )\n resolved = resolve_llm_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.provider_name == \"aihubmix\"\n assert resolved.provider_mode == \"gateway\"\n assert resolved.effective_url == \"https://api.aihubmix.com/v1\"\n assert resolved.extra_headers == {\"APP-Code\": \"x\"}\n\n\ndef test_llm_local_fallback(tmp_path: Path) -> None:\n catalog = _build_catalog(\n llm_profile={\n \"id\": \"llm-p\",\n \"name\": \"LLM\",\n \"binding\": \"\",\n \"base_url\": \"http://localhost:11434/v1\",\n \"api_key\": \"\",\n \"api_version\": \"\",\n \"extra_headers\": {},\n \"models\": [{\"id\": \"llm-m\", \"name\": \"m\", \"model\": \"llama3.2\"}],\n }\n )\n resolved = resolve_llm_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.provider_name == \"ollama\"\n assert resolved.provider_mode == \"local\"\n assert resolved.api_key == \"sk-no-key-required\"\n\n\ndef test_search_fallback_to_duckduckgo_without_key(tmp_path: Path) -> None:\n catalog = _build_catalog(\n search_profile={\n \"id\": \"search-p\",\n \"name\": \"Search\",\n \"provider\": \"brave\",\n \"base_url\": \"\",\n \"api_key\": \"\",\n \"proxy\": \"http://127.0.0.1:7890\",\n \"models\": [],\n }\n )\n resolved = resolve_search_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.provider == \"duckduckgo\"\n assert resolved.requested_provider == \"brave\"\n assert resolved.fallback_reason is not None\n assert resolved.proxy == \"http://127.0.0.1:7890\"\n\n\ndef test_search_marks_deprecated_provider(tmp_path: Path) -> None:\n catalog = _build_catalog(\n search_profile={\n \"id\": \"search-p\",\n \"name\": \"Search\",\n \"provider\": \"exa\",\n \"base_url\": \"\",\n \"api_key\": \"k\",\n \"proxy\": \"\",\n \"models\": [],\n }\n )\n resolved = resolve_search_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.unsupported_provider is True\n assert resolved.deprecated_provider is True\n assert resolved.provider == \"exa\"\n\n\ndef test_search_perplexity_missing_credentials(tmp_path: Path) -> None:\n catalog = _build_catalog(\n search_profile={\n \"id\": \"search-p\",\n \"name\": \"Search\",\n \"provider\": \"perplexity\",\n \"base_url\": \"\",\n \"api_key\": \"\",\n \"proxy\": \"\",\n \"models\": [],\n }\n )\n resolved = resolve_search_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.provider == \"perplexity\"\n assert resolved.unsupported_provider is False\n assert resolved.deprecated_provider is False\n assert resolved.missing_credentials is True\n\n\ndef test_search_serper_missing_credentials(tmp_path: Path) -> None:\n catalog = _build_catalog(\n search_profile={\n \"id\": \"search-p\",\n \"name\": \"Search\",\n \"provider\": \"serper\",\n \"base_url\": \"\",\n \"api_key\": \"\",\n \"proxy\": \"\",\n \"models\": [],\n }\n )\n resolved = resolve_search_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.provider == \"serper\"\n assert resolved.unsupported_provider is False\n assert resolved.deprecated_provider is False\n assert resolved.missing_credentials is True\n\n\ndef test_search_searxng_without_url_fallback(tmp_path: Path) -> None:\n catalog = _build_catalog(\n search_profile={\n \"id\": \"search-p\",\n \"name\": \"Search\",\n \"provider\": \"searxng\",\n \"base_url\": \"\",\n \"api_key\": \"\",\n \"proxy\": \"\",\n \"models\": [],\n }\n )\n resolved = resolve_search_runtime_config(catalog=catalog, env_store=_empty_env(tmp_path))\n assert resolved.provider == \"duckduckgo\"\n assert resolved.fallback_reason is not None\n"}}}}