{"repo": "bodud/quantgodbackend", "files": {"tools/ai_analysis/providers/router.py": {"06f196b4e6504c7206b3bce2b926bdaa2bf6a7b2": {"t": 1785745434.4376667, "n": 4742, "h": "7950556ac74db3db", "c": "from __future__ import annotations\n\nimport os\nfrom pathlib import Path\nfrom typing import Any, Mapping\n\nfrom .base import AIProviderConfig, AIProviderError, assert_ai_provider_safety, read_env_file, truthy\nfrom .deepseek_provider import DeepSeekProvider\nfrom .mock_provider import MockProvider\nfrom .openrouter_provider import OpenRouterProvider\n\nSUPPORTED_MODELS = {\n \"deepseek\": [\"deepseek-v4-flash\", \"deepseek-v4-pro\"],\n \"openrouter\": [\"anthropic/claude-sonnet-4-20250514\", \"deepseek/deepseek-v4-flash\"],\n \"mock\": [\"mock-local\"],\n}\n\nDEFAULT_BASE_URLS = {\n \"deepseek\": \"https://api.deepseek.com\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"mock\": \"mock://local\",\n}\n\n\ndef _first(*values: Any, fallback: str = \"\") -> str:\n for value in values:\n if value is None:\n continue\n text = str(value).strip()\n if text:\n return text\n return fallback\n\n\ndef _int(value: Any, fallback: int) -> int:\n try:\n return int(value)\n except (TypeError, ValueError):\n return fallback\n\n\ndef _float(value: Any, fallback: float) -> float:\n try:\n return float(value)\n except (TypeError, ValueError):\n return fallback\n\n\ndef load_ai_provider_config(\n *,\n repo_root: str | Path | None = None,\n env_file: str | Path | None = None,\n environ: Mapping[str, str] | None = None,\n) -> AIProviderConfig:\n env = dict(os.environ if environ is None else environ)\n root = Path(repo_root or Path.cwd()).resolve()\n\n ai_env_path = Path(env_file or env.get(\"QG_AI_ENV_FILE\") or root / \".env.ai.local\").expanduser()\n deepseek_env_path = Path(env.get(\"QG_MT5_AI_DEEPSEEK_ENV_FILE\") or root / \".env.deepseek.local\").expanduser()\n\n merged = {}\n merged.update(read_env_file(deepseek_env_path))\n merged.update(read_env_file(ai_env_path))\n merged.update(env)\n\n assert_ai_provider_safety(merged)\n\n provider = _first(merged.get(\"QG_AI_PROVIDER\"), fallback=\"deepseek\").lower()\n if provider not in SUPPORTED_MODELS:\n raise AIProviderError(f\"unsupported AI provider: {provider}\")\n\n api_key = _first(\n merged.get(\"QG_AI_API_KEY\"),\n merged.get(\"DEEPSEEK_API_KEY\"),\n merged.get(\"QG_DEEPSEEK_API_KEY\"),\n merged.get(\"QG_MT5_AI_DEEPSEEK_API_KEY\"),\n merged.get(\"OPENROUTER_API_KEY\"),\n merged.get(\"QG_OPENROUTER_API_KEY\"),\n fallback=\"\",\n )\n enabled_default = bool(api_key.strip()) or provider == \"mock\"\n enabled = truthy(merged.get(\"QG_AI_ENABLED\", merged.get(\"QG_MT5_AI_DEEPSEEK_ENABLED\")), enabled_default)\n\n default_model = SUPPORTED_MODELS[provider][0]\n model = _first(\n merged.get(\"QG_AI_MODEL\"),\n merged.get(\"QG_MT5_AI_DEEPSEEK_MODEL\"),\n merged.get(\"DEEPSEEK_MODEL\"),\n merged.get(\"OPENROUTER_MODEL\"),\n fallback=default_model,\n )\n base_url = _first(\n merged.get(\"QG_AI_BASE_URL\"),\n merged.get(\"QG_DEEPSEEK_BASE_URL\"),\n merged.get(\"DEEPSEEK_BASE_URL\"),\n merged.get(\"OPENROUTER_BASE_URL\"),\n fallback=DEFAULT_BASE_URLS[provider],\n )\n\n return AIProviderConfig(\n enabled=enabled,\n provider=provider,\n model=model,\n base_url=base_url,\n api_key=api_key,\n timeout_seconds=_int(merged.get(\"QG_AI_TIMEOUT_SECONDS\") or merged.get(\"QG_MT5_AI_DEEPSEEK_TIMEOUT_SECONDS\"), 45),\n max_tokens=_int(merged.get(\"QG_AI_MAX_TOKENS\") or merged.get(\"QG_MT5_AI_DEEPSEEK_MAX_TOKENS\"), 1800),\n temperature=_float(merged.get(\"QG_AI_TEMPERATURE\") or merged.get(\"QG_MT5_AI_DEEPSEEK_TEMPERATURE\"), 0.25),\n require_json=truthy(merged.get(\"QG_AI_REQUIRE_JSON\"), True),\n daily_call_limit=_int(merged.get(\"QG_AI_DAILY_CALL_LIMIT\"), 50),\n daily_budget_usd=_float(merged.get(\"QG_AI_DAILY_BUDGET_USD\"), 2.0),\n env_file=str(ai_env_path),\n )\n\n\ndef load_ai_provider(config: AIProviderConfig | None = None, **kwargs: Any): # type: ignore[no-untyped-def]\n cfg = config or load_ai_provider_config(**kwargs)\n if cfg.provider == \"mock\":\n return MockProvider(cfg)\n if cfg.provider == \"openrouter\":\n return OpenRouterProvider(cfg)\n if cfg.provider == \"deepseek\":\n return DeepSeekProvider(cfg)\n raise AIProviderError(f\"unsupported AI provider: {cfg.provider}\")\n\n\ndef supported_models_payload() -> dict[str, Any]:\n return {\n \"schema\": \"quantgod.ai_provider.models.v1\",\n \"supportedProviders\": sorted(SUPPORTED_MODELS.keys()),\n \"models\": SUPPORTED_MODELS,\n \"defaultBaseUrls\": DEFAULT_BASE_URLS,\n \"safety\": {\n \"advisoryOnly\": True,\n \"researchOnly\": True,\n \"orderSendAllowed\": False,\n \"credentialStorageAllowed\": False,\n },\n }\n"}, "4d496ceba5b64b76e3ae9247eb078b95fe8bf897": {"t": 1785753178.6248565, "n": 4742, "h": "7950556ac74db3db", "c": "from __future__ import annotations\n\nimport os\nfrom pathlib import Path\nfrom typing import Any, Mapping\n\nfrom .base import AIProviderConfig, AIProviderError, assert_ai_provider_safety, read_env_file, truthy\nfrom .deepseek_provider import DeepSeekProvider\nfrom .mock_provider import MockProvider\nfrom .openrouter_provider import OpenRouterProvider\n\nSUPPORTED_MODELS = {\n \"deepseek\": [\"deepseek-v4-flash\", \"deepseek-v4-pro\"],\n \"openrouter\": [\"anthropic/claude-sonnet-4-20250514\", \"deepseek/deepseek-v4-flash\"],\n \"mock\": [\"mock-local\"],\n}\n\nDEFAULT_BASE_URLS = {\n \"deepseek\": \"https://api.deepseek.com\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"mock\": \"mock://local\",\n}\n\n\ndef _first(*values: Any, fallback: str = \"\") -> str:\n for value in values:\n if value is None:\n continue\n text = str(value).strip()\n if text:\n return text\n return fallback\n\n\ndef _int(value: Any, fallback: int) -> int:\n try:\n return int(value)\n except (TypeError, ValueError):\n return fallback\n\n\ndef _float(value: Any, fallback: float) -> float:\n try:\n return float(value)\n except (TypeError, ValueError):\n return fallback\n\n\ndef load_ai_provider_config(\n *,\n repo_root: str | Path | None = None,\n env_file: str | Path | None = None,\n environ: Mapping[str, str] | None = None,\n) -> AIProviderConfig:\n env = dict(os.environ if environ is None else environ)\n root = Path(repo_root or Path.cwd()).resolve()\n\n ai_env_path = Path(env_file or env.get(\"QG_AI_ENV_FILE\") or root / \".env.ai.local\").expanduser()\n deepseek_env_path = Path(env.get(\"QG_MT5_AI_DEEPSEEK_ENV_FILE\") or root / \".env.deepseek.local\").expanduser()\n\n merged = {}\n merged.update(read_env_file(deepseek_env_path))\n merged.update(read_env_file(ai_env_path))\n merged.update(env)\n\n assert_ai_provider_safety(merged)\n\n provider = _first(merged.get(\"QG_AI_PROVIDER\"), fallback=\"deepseek\").lower()\n if provider not in SUPPORTED_MODELS:\n raise AIProviderError(f\"unsupported AI provider: {provider}\")\n\n api_key = _first(\n merged.get(\"QG_AI_API_KEY\"),\n merged.get(\"DEEPSEEK_API_KEY\"),\n merged.get(\"QG_DEEPSEEK_API_KEY\"),\n merged.get(\"QG_MT5_AI_DEEPSEEK_API_KEY\"),\n merged.get(\"OPENROUTER_API_KEY\"),\n merged.get(\"QG_OPENROUTER_API_KEY\"),\n fallback=\"\",\n )\n enabled_default = bool(api_key.strip()) or provider == \"mock\"\n enabled = truthy(merged.get(\"QG_AI_ENABLED\", merged.get(\"QG_MT5_AI_DEEPSEEK_ENABLED\")), enabled_default)\n\n default_model = SUPPORTED_MODELS[provider][0]\n model = _first(\n merged.get(\"QG_AI_MODEL\"),\n merged.get(\"QG_MT5_AI_DEEPSEEK_MODEL\"),\n merged.get(\"DEEPSEEK_MODEL\"),\n merged.get(\"OPENROUTER_MODEL\"),\n fallback=default_model,\n )\n base_url = _first(\n merged.get(\"QG_AI_BASE_URL\"),\n merged.get(\"QG_DEEPSEEK_BASE_URL\"),\n merged.get(\"DEEPSEEK_BASE_URL\"),\n merged.get(\"OPENROUTER_BASE_URL\"),\n fallback=DEFAULT_BASE_URLS[provider],\n )\n\n return AIProviderConfig(\n enabled=enabled,\n provider=provider,\n model=model,\n base_url=base_url,\n api_key=api_key,\n timeout_seconds=_int(merged.get(\"QG_AI_TIMEOUT_SECONDS\") or merged.get(\"QG_MT5_AI_DEEPSEEK_TIMEOUT_SECONDS\"), 45),\n max_tokens=_int(merged.get(\"QG_AI_MAX_TOKENS\") or merged.get(\"QG_MT5_AI_DEEPSEEK_MAX_TOKENS\"), 1800),\n temperature=_float(merged.get(\"QG_AI_TEMPERATURE\") or merged.get(\"QG_MT5_AI_DEEPSEEK_TEMPERATURE\"), 0.25),\n require_json=truthy(merged.get(\"QG_AI_REQUIRE_JSON\"), True),\n daily_call_limit=_int(merged.get(\"QG_AI_DAILY_CALL_LIMIT\"), 50),\n daily_budget_usd=_float(merged.get(\"QG_AI_DAILY_BUDGET_USD\"), 2.0),\n env_file=str(ai_env_path),\n )\n\n\ndef load_ai_provider(config: AIProviderConfig | None = None, **kwargs: Any): # type: ignore[no-untyped-def]\n cfg = config or load_ai_provider_config(**kwargs)\n if cfg.provider == \"mock\":\n return MockProvider(cfg)\n if cfg.provider == \"openrouter\":\n return OpenRouterProvider(cfg)\n if cfg.provider == \"deepseek\":\n return DeepSeekProvider(cfg)\n raise AIProviderError(f\"unsupported AI provider: {cfg.provider}\")\n\n\ndef supported_models_payload() -> dict[str, Any]:\n return {\n \"schema\": \"quantgod.ai_provider.models.v1\",\n \"supportedProviders\": sorted(SUPPORTED_MODELS.keys()),\n \"models\": SUPPORTED_MODELS,\n \"defaultBaseUrls\": DEFAULT_BASE_URLS,\n \"safety\": {\n \"advisoryOnly\": True,\n \"researchOnly\": True,\n \"orderSendAllowed\": False,\n \"credentialStorageAllowed\": False,\n },\n }\n"}, "944fc4d9efe04f407276b6d7668825081e106eea": {"t": 1786067377.02553, "n": 4742, "h": "7950556ac74db3db", "c": "from __future__ import annotations\n\nimport os\nfrom pathlib import Path\nfrom typing import Any, Mapping\n\nfrom .base import AIProviderConfig, AIProviderError, assert_ai_provider_safety, read_env_file, truthy\nfrom .deepseek_provider import DeepSeekProvider\nfrom .mock_provider import MockProvider\nfrom .openrouter_provider import OpenRouterProvider\n\nSUPPORTED_MODELS = {\n \"deepseek\": [\"deepseek-v4-flash\", \"deepseek-v4-pro\"],\n \"openrouter\": [\"anthropic/claude-sonnet-4-20250514\", \"deepseek/deepseek-v4-flash\"],\n \"mock\": [\"mock-local\"],\n}\n\nDEFAULT_BASE_URLS = {\n \"deepseek\": \"https://api.deepseek.com\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"mock\": \"mock://local\",\n}\n\n\ndef _first(*values: Any, fallback: str = \"\") -> str:\n for value in values:\n if value is None:\n continue\n text = str(value).strip()\n if text:\n return text\n return fallback\n\n\ndef _int(value: Any, fallback: int) -> int:\n try:\n return int(value)\n except (TypeError, ValueError):\n return fallback\n\n\ndef _float(value: Any, fallback: float) -> float:\n try:\n return float(value)\n except (TypeError, ValueError):\n return fallback\n\n\ndef load_ai_provider_config(\n *,\n repo_root: str | Path | None = None,\n env_file: str | Path | None = None,\n environ: Mapping[str, str] | None = None,\n) -> AIProviderConfig:\n env = dict(os.environ if environ is None else environ)\n root = Path(repo_root or Path.cwd()).resolve()\n\n ai_env_path = Path(env_file or env.get(\"QG_AI_ENV_FILE\") or root / \".env.ai.local\").expanduser()\n deepseek_env_path = Path(env.get(\"QG_MT5_AI_DEEPSEEK_ENV_FILE\") or root / \".env.deepseek.local\").expanduser()\n\n merged = {}\n merged.update(read_env_file(deepseek_env_path))\n merged.update(read_env_file(ai_env_path))\n merged.update(env)\n\n assert_ai_provider_safety(merged)\n\n provider = _first(merged.get(\"QG_AI_PROVIDER\"), fallback=\"deepseek\").lower()\n if provider not in SUPPORTED_MODELS:\n raise AIProviderError(f\"unsupported AI provider: {provider}\")\n\n api_key = _first(\n merged.get(\"QG_AI_API_KEY\"),\n merged.get(\"DEEPSEEK_API_KEY\"),\n merged.get(\"QG_DEEPSEEK_API_KEY\"),\n merged.get(\"QG_MT5_AI_DEEPSEEK_API_KEY\"),\n merged.get(\"OPENROUTER_API_KEY\"),\n merged.get(\"QG_OPENROUTER_API_KEY\"),\n fallback=\"\",\n )\n enabled_default = bool(api_key.strip()) or provider == \"mock\"\n enabled = truthy(merged.get(\"QG_AI_ENABLED\", merged.get(\"QG_MT5_AI_DEEPSEEK_ENABLED\")), enabled_default)\n\n default_model = SUPPORTED_MODELS[provider][0]\n model = _first(\n merged.get(\"QG_AI_MODEL\"),\n merged.get(\"QG_MT5_AI_DEEPSEEK_MODEL\"),\n merged.get(\"DEEPSEEK_MODEL\"),\n merged.get(\"OPENROUTER_MODEL\"),\n fallback=default_model,\n )\n base_url = _first(\n merged.get(\"QG_AI_BASE_URL\"),\n merged.get(\"QG_DEEPSEEK_BASE_URL\"),\n merged.get(\"DEEPSEEK_BASE_URL\"),\n merged.get(\"OPENROUTER_BASE_URL\"),\n fallback=DEFAULT_BASE_URLS[provider],\n )\n\n return AIProviderConfig(\n enabled=enabled,\n provider=provider,\n model=model,\n base_url=base_url,\n api_key=api_key,\n timeout_seconds=_int(merged.get(\"QG_AI_TIMEOUT_SECONDS\") or merged.get(\"QG_MT5_AI_DEEPSEEK_TIMEOUT_SECONDS\"), 45),\n max_tokens=_int(merged.get(\"QG_AI_MAX_TOKENS\") or merged.get(\"QG_MT5_AI_DEEPSEEK_MAX_TOKENS\"), 1800),\n temperature=_float(merged.get(\"QG_AI_TEMPERATURE\") or merged.get(\"QG_MT5_AI_DEEPSEEK_TEMPERATURE\"), 0.25),\n require_json=truthy(merged.get(\"QG_AI_REQUIRE_JSON\"), True),\n daily_call_limit=_int(merged.get(\"QG_AI_DAILY_CALL_LIMIT\"), 50),\n daily_budget_usd=_float(merged.get(\"QG_AI_DAILY_BUDGET_USD\"), 2.0),\n env_file=str(ai_env_path),\n )\n\n\ndef load_ai_provider(config: AIProviderConfig | None = None, **kwargs: Any): # type: ignore[no-untyped-def]\n cfg = config or load_ai_provider_config(**kwargs)\n if cfg.provider == \"mock\":\n return MockProvider(cfg)\n if cfg.provider == \"openrouter\":\n return OpenRouterProvider(cfg)\n if cfg.provider == \"deepseek\":\n return DeepSeekProvider(cfg)\n raise AIProviderError(f\"unsupported AI provider: {cfg.provider}\")\n\n\ndef supported_models_payload() -> dict[str, Any]:\n return {\n \"schema\": \"quantgod.ai_provider.models.v1\",\n \"supportedProviders\": sorted(SUPPORTED_MODELS.keys()),\n \"models\": SUPPORTED_MODELS,\n \"defaultBaseUrls\": DEFAULT_BASE_URLS,\n \"safety\": {\n \"advisoryOnly\": True,\n \"researchOnly\": True,\n \"orderSendAllowed\": False,\n \"credentialStorageAllowed\": False,\n },\n }\n"}, "1cc48de98bd971f968af695f0ef88d5332397599": {"t": 1786068245.7983112, "n": 4742, "h": "7950556ac74db3db", "c": "from __future__ import annotations\n\nimport os\nfrom pathlib import Path\nfrom typing import Any, Mapping\n\nfrom .base import AIProviderConfig, AIProviderError, assert_ai_provider_safety, read_env_file, truthy\nfrom .deepseek_provider import DeepSeekProvider\nfrom .mock_provider import MockProvider\nfrom .openrouter_provider import OpenRouterProvider\n\nSUPPORTED_MODELS = {\n \"deepseek\": [\"deepseek-v4-flash\", \"deepseek-v4-pro\"],\n \"openrouter\": [\"anthropic/claude-sonnet-4-20250514\", \"deepseek/deepseek-v4-flash\"],\n \"mock\": [\"mock-local\"],\n}\n\nDEFAULT_BASE_URLS = {\n \"deepseek\": \"https://api.deepseek.com\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"mock\": \"mock://local\",\n}\n\n\ndef _first(*values: Any, fallback: str = \"\") -> str:\n for value in values:\n if value is None:\n continue\n text = str(value).strip()\n if text:\n return text\n return fallback\n\n\ndef _int(value: Any, fallback: int) -> int:\n try:\n return int(value)\n except (TypeError, ValueError):\n return fallback\n\n\ndef _float(value: Any, fallback: float) -> float:\n try:\n return float(value)\n except (TypeError, ValueError):\n return fallback\n\n\ndef load_ai_provider_config(\n *,\n repo_root: str | Path | None = None,\n env_file: str | Path | None = None,\n environ: Mapping[str, str] | None = None,\n) -> AIProviderConfig:\n env = dict(os.environ if environ is None else environ)\n root = Path(repo_root or Path.cwd()).resolve()\n\n ai_env_path = Path(env_file or env.get(\"QG_AI_ENV_FILE\") or root / \".env.ai.local\").expanduser()\n deepseek_env_path = Path(env.get(\"QG_MT5_AI_DEEPSEEK_ENV_FILE\") or root / \".env.deepseek.local\").expanduser()\n\n merged = {}\n merged.update(read_env_file(deepseek_env_path))\n merged.update(read_env_file(ai_env_path))\n merged.update(env)\n\n assert_ai_provider_safety(merged)\n\n provider = _first(merged.get(\"QG_AI_PROVIDER\"), fallback=\"deepseek\").lower()\n if provider not in SUPPORTED_MODELS:\n raise AIProviderError(f\"unsupported AI provider: {provider}\")\n\n api_key = _first(\n merged.get(\"QG_AI_API_KEY\"),\n merged.get(\"DEEPSEEK_API_KEY\"),\n merged.get(\"QG_DEEPSEEK_API_KEY\"),\n merged.get(\"QG_MT5_AI_DEEPSEEK_API_KEY\"),\n merged.get(\"OPENROUTER_API_KEY\"),\n merged.get(\"QG_OPENROUTER_API_KEY\"),\n fallback=\"\",\n )\n enabled_default = bool(api_key.strip()) or provider == \"mock\"\n enabled = truthy(merged.get(\"QG_AI_ENABLED\", merged.get(\"QG_MT5_AI_DEEPSEEK_ENABLED\")), enabled_default)\n\n default_model = SUPPORTED_MODELS[provider][0]\n model = _first(\n merged.get(\"QG_AI_MODEL\"),\n merged.get(\"QG_MT5_AI_DEEPSEEK_MODEL\"),\n merged.get(\"DEEPSEEK_MODEL\"),\n merged.get(\"OPENROUTER_MODEL\"),\n fallback=default_model,\n )\n base_url = _first(\n merged.get(\"QG_AI_BASE_URL\"),\n merged.get(\"QG_DEEPSEEK_BASE_URL\"),\n merged.get(\"DEEPSEEK_BASE_URL\"),\n merged.get(\"OPENROUTER_BASE_URL\"),\n fallback=DEFAULT_BASE_URLS[provider],\n )\n\n return AIProviderConfig(\n enabled=enabled,\n provider=provider,\n model=model,\n base_url=base_url,\n api_key=api_key,\n timeout_seconds=_int(merged.get(\"QG_AI_TIMEOUT_SECONDS\") or merged.get(\"QG_MT5_AI_DEEPSEEK_TIMEOUT_SECONDS\"), 45),\n max_tokens=_int(merged.get(\"QG_AI_MAX_TOKENS\") or merged.get(\"QG_MT5_AI_DEEPSEEK_MAX_TOKENS\"), 1800),\n temperature=_float(merged.get(\"QG_AI_TEMPERATURE\") or merged.get(\"QG_MT5_AI_DEEPSEEK_TEMPERATURE\"), 0.25),\n require_json=truthy(merged.get(\"QG_AI_REQUIRE_JSON\"), True),\n daily_call_limit=_int(merged.get(\"QG_AI_DAILY_CALL_LIMIT\"), 50),\n daily_budget_usd=_float(merged.get(\"QG_AI_DAILY_BUDGET_USD\"), 2.0),\n env_file=str(ai_env_path),\n )\n\n\ndef load_ai_provider(config: AIProviderConfig | None = None, **kwargs: Any): # type: ignore[no-untyped-def]\n cfg = config or load_ai_provider_config(**kwargs)\n if cfg.provider == \"mock\":\n return MockProvider(cfg)\n if cfg.provider == \"openrouter\":\n return OpenRouterProvider(cfg)\n if cfg.provider == \"deepseek\":\n return DeepSeekProvider(cfg)\n raise AIProviderError(f\"unsupported AI provider: {cfg.provider}\")\n\n\ndef supported_models_payload() -> dict[str, Any]:\n return {\n \"schema\": \"quantgod.ai_provider.models.v1\",\n \"supportedProviders\": sorted(SUPPORTED_MODELS.keys()),\n \"models\": SUPPORTED_MODELS,\n \"defaultBaseUrls\": DEFAULT_BASE_URLS,\n \"safety\": {\n \"advisoryOnly\": True,\n \"researchOnly\": True,\n \"orderSendAllowed\": False,\n \"credentialStorageAllowed\": False,\n },\n }\n"}}}}