"""大模型回退层:Mock 与 OpenAI 兼容 API 两种后端。""" from __future__ import annotations import asyncio from typing import Dict from .config import get_api_key from .llm_client import OpenAICompatClient from .models import ExpertResponse class FallbackProvider: name: str = "fallback" async def generate(self, query: str) -> ExpertResponse: raise NotImplementedError class MockFallback(FallbackProvider): """确定性 mock 大模型:标识为 fallback,便于测试升级路径。""" def __init__(self, model: str = "mock-large"): self.model = model self.name = f"fallback-{model}" async def generate(self, query: str) -> ExpertResponse: await asyncio.sleep(0.002) body = ( f"(大模型回退)「{query}」\n\n" "这是一条来自大模型回退路径的完整回答。\n" "要点:\n" "1. 对复杂/跨域任务给出综合推理\n" "2. 补充领域专家未覆盖的上下文\n" "3. 给出可执行的后续建议\n" ) return ExpertResponse( text=body, model_used=self.model, latency_ms=2.0, tokens=120, cost_est=2.0 * 120 / 1_000_000, ) class APIFallback(FallbackProvider): """OpenAI 兼容大模型 API(如 DeepSeek / OpenAI / 本地 vLLM)。""" def __init__(self, model: str, base_url: str, api_key: str): self.model = model self.name = f"fallback-{model}" self._client = OpenAICompatClient(base_url, api_key, timeout=90.0) async def generate(self, query: str) -> ExpertResponse: data = await self._client.chat( self.model, [{"role": "user", "content": query}], temperature=0.3, max_tokens=2048, ) body = self._client.completion_text(data) tokens = self._client.completion_tokens(data, body) return ExpertResponse( text=body, model_used=self.model, latency_ms=0.0, tokens=tokens, cost_est=2.0 * tokens / 1_000_000, ) def build_fallback(cfg: Dict) -> FallbackProvider: """cfg 为 fallback 段配置。""" ftype = cfg.get("type", "mock") model = cfg.get("model", "deepseek-chat") if ftype == "mock": return MockFallback(model=model) if ftype == "api": api_key = cfg.get("api_key") or get_api_key(cfg) if not api_key: raise RuntimeError( f"APIFallback 缺少 API Key:请设置环境变量 {cfg.get('api_key_env')} 或配置 api_key" ) return APIFallback(model, cfg.get("base_url", "https://api.deepseek.com/v1"), api_key) raise ValueError(f"未知 fallback 类型: {ftype}(支持 mock | api)")