Files
projectAIpopular/router_system/fallback.py
T

186 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""大模型回退层(最后处理者):Mock / 降级模板 / 本地模型 / OpenAI 兼容 API。
- NoneFallback :降级模板(零参数,明确告知超出知识库范围)—— L0 最小可用
- MockFallback :确定性 mock 大模型(零参数,测试升级路径用)
- LocalFallback :本地小模型(≤8BQ4 量化,OpenAI 兼容端点如 Ollama/vLLM)—— 按需加载
- APIFallback :远程 OpenAI 兼容 API(可选,默认关闭)
"""
from __future__ import annotations
import asyncio
from typing import Dict, Optional
from .models import ExpertResponse
class FallbackProvider:
name: str = "fallback"
async def generate(self, query: str) -> ExpertResponse:
raise NotImplementedError
class NoneFallback(FallbackProvider):
"""降级模板:零参数兜底,明确告知查询超出知识库范围。"""
def __init__(self, model: str = "none"):
self.model = model
self.name = "fallback-none"
async def generate(self, query: str) -> ExpertResponse:
body = (
f"(降级响应)「{query}\n\n"
"当前查询超出本地知识库可处理范围(低置信度或质量校验未通过)。\n"
"可选处理:\n"
"1. 换个更明确的问法重试\n"
"2. 启用 L2 本地小模型或配置最后处理者(fallback.type: local\n"
)
return ExpertResponse(
text=body,
model_used=self.model,
latency_ms=0.0,
tokens=80,
cost_est=0.0,
)
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 LocalFallback(FallbackProvider):
"""本地小模型最后处理者(≤8B,如 DeepSeek-R1-Distill-Qwen-7B Q4)。
通过 OpenAI 兼容端点调用(Ollama 默认 11434/v1vLLM 默认 8001/v1),
模型按需加载、用完即卸载(由本地推理服务管理),不常驻显存。
"""
def __init__(self, model: str, base_url: str = "http://127.0.0.1:11434/v1",
api_key: str = ""):
self.model = model
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.name = f"fallback-local-{model}"
self._client = None
def _get_client(self):
if self._client is None:
import httpx
self._client = httpx.AsyncClient(timeout=120.0)
return self._client
async def generate(self, query: str) -> ExpertResponse:
client = self._get_client()
headers = {"Authorization": f"Bearer {self.api_key}"} if self.api_key else {}
resp = await client.post(
f"{self.base_url}/chat/completions",
headers=headers,
json={
"model": self.model,
"messages": [{"role": "user", "content": query}],
"temperature": 0.3,
"max_tokens": 2048,
},
)
resp.raise_for_status()
data = resp.json()
body = data["choices"][0]["message"]["content"]
usage = data.get("usage", {})
tokens = usage.get("completion_tokens", int(len(body) / 2.2))
return ExpertResponse(
text=body,
model_used=self.model,
latency_ms=0.0,
tokens=tokens,
cost_est=0.0, # 本地推理成本按电费计,模型层成本记为 0(相对 API)
)
class APIFallback(FallbackProvider):
"""OpenAI 兼容大模型 API(如 DeepSeek / OpenAI / 本地 vLLM)。"""
def __init__(self, model: str, base_url: str, api_key: str):
self.model = model
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.name = f"fallback-{model}"
self._client = None
def _get_client(self):
if self._client is None:
import httpx
self._client = httpx.AsyncClient(timeout=90.0)
return self._client
async def generate(self, query: str) -> ExpertResponse:
client = self._get_client()
resp = await client.post(
f"{self.base_url}/chat/completions",
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"model": self.model,
"messages": [{"role": "user", "content": query}],
"temperature": 0.3,
"max_tokens": 2048,
},
)
resp.raise_for_status()
data = resp.json()
body = data["choices"][0]["message"]["content"]
usage = data.get("usage", {})
tokens = usage.get("completion_tokens", int(len(body) / 2.2))
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-v4-flash")
if ftype == "none":
return NoneFallback(model=model)
if ftype == "mock":
return MockFallback(model=model)
if ftype == "local":
return LocalFallback(
model,
cfg.get("base_url", "http://127.0.0.1:11434/v1"),
cfg.get("api_key", ""),
)
if ftype == "api":
import os
api_key = cfg.get("api_key") or os.environ.get(cfg.get("api_key_env", "API_KEY"))
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"), api_key)
raise ValueError(f"未知 fallback 类型: {ftype}(支持 none | mock | local | api")