bbqddt2/Antigravity
0
1# -*- coding: utf-8 -*-2"""3Antigravity LLM创新引擎 V3.0 — 全渠道AI协同4 5支持的所有AI供应商:60. CC Switch (最高优先级) - 本地代理 http://127.0.0.1:15721,路由Claude/Gemini/Codex71. OpenRouter (已接入) - 免费模型: qwen/gemma/llama等82. Gemini (已接入) - 免费层配额用完,自动降级93. DeepSeek (已接入) - 余额不足,自动降级104. Ollama (本地) - 需要安装,支持gemma/llama等115. Together AI (预留) - 需要API Key126. Perplexity (预留) - 需要API Key137. Groq (预留) - 需要API Key148. HuggingFace (预留) - 通过HF_TOKEN访问15 16自动故障转移: 按优先级依次尝试,一个失败自动换下一个17"""18import os19import json20import time21from pathlib import Path22from typing import Optional, List, Dict, Any23from datetime import datetime24 25_PROJECT_ROOT = Path(__file__).resolve().parent.parent26 27 28class LLMEngine:29 """全渠道LLM引擎 — 多供应商自动故障转移"""30 31 def __init__(self):32 self.providers = {}33 self.active_provider = None34 self.available = False35 self.call_log = [] # 记录每次调用36 37 # 从.env加载密钥38 self._load_env()39 40 # 初始化所有可用供应商 — CC Switch最高优先级41 self._init_cc_switch() # ★ 最高优先级:CC Switch本地代理42 self._init_ollama()43 self._init_openrouter()44 self._init_deeprouter()45 self._init_sensenova()46 self._init_gemini()47 self._init_deepseek()48 self._init_together()49 self._init_perplexity()50 self._init_groq()51 self._init_huggingface()52 53 # 重新排序:CC Switch > Ollama54 if "cc_switch" in self.providers:55 self.providers["cc_switch"]["priority"] = -1 # 最高优先56 if "ollama" in self.providers:57 self.providers["ollama"]["priority"] = 058 59 # 确定可用供应商60 self._build_provider_list()61 62 def _load_env(self):63 """从.env文件加载所有密钥"""64 env_path = _PROJECT_ROOT / ".env"65 if env_path.exists():66 with open(env_path, "r", encoding="utf-8") as f:67 for line in f:68 line = line.strip()69 if line.startswith("#") or "=" not in line:70 continue71 key, _, value = line.partition("=")72 key = key.strip()73 value = value.strip()74 if value and not os.environ.get(key):75 os.environ[key] = value76 77 def _init_openrouter(self):78 """初始化OpenRouter"""79 or_key = os.environ.get("OPENROUTER_API_KEY")80 if or_key:81 try:82 from openai import OpenAI83 self.providers["openrouter"] = {84 "client": OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key),85 "models": [86 "qwen/qwen-2.5-72b-instruct",87 "meta-llama/llama-3.3-70b-instruct",88 "google/gemma-2-27b-it",89 "mistralai/mistral-7b-instruct",90 ],91 "priority": 1,92 }93 self.available = True94 except Exception:95 pass96 97 def _init_deeprouter(self):98 """初始化DeepRouter — 多key轮换"""99 for i in range(1, 5):100 key = os.environ.get(f"DEEPRouter_API_KEY_{i}")101 if key:102 try:103 from openai import OpenAI104 self.providers["deeprouter"] = {105 "client": OpenAI(api_key=key, base_url="https://www.deeprouter.top/v1"),106 "models": ["gpt-4o-mini", "gpt-3.5-turbo", "claude-3-haiku-20240307"],107 "priority": 2,108 }109 if not self.available:110 self.available = True111 break112 except Exception:113 continue114 115 def _init_sensenova(self):116 """初始化SenseNova — 多key轮换"""117 for i in range(1, 5):118 key = os.environ.get(f"SENSENOVA_API_KEY_{i}")119 if key:120 try:121 from openai import OpenAI122 self.providers["sensenova"] = {123 "client": OpenAI(api_key=key, base_url="https://api.sensenova.cn/v1"),124 "models": ["yi-lightning", "yi-large"],125 "priority": 4,126 }127 if not self.available:128 self.available = True129 break130 except Exception:131 pass132 133 def _init_gemini(self):134 """初始化Gemini"""135 try:136 from llm_innovation.llm_client import GeminiClient137 gc = GeminiClient()138 if gc.available:139 self.providers["gemini"] = {140 "client": gc,141 "priority": 2,142 }143 if not self.available:144 self.available = True145 except Exception:146 pass147 148 def _init_deepseek(self):149 """初始化DeepSeek"""150 ds_key = os.environ.get("DEEPSEEK_API_KEY")151 if ds_key:152 try:153 from openai import OpenAI154 self.providers["deepseek"] = {155 "client": OpenAI(api_key=ds_key, base_url="https://api.deepseek.com/v1"),156 "models": ["deepseek-chat", "deepseek-coder"],157 "priority": 3,158 }159 if not self.available:160 self.available = True161 except Exception:162 pass163 164 def _init_together(self):165 """初始化Together AI"""166 tg_key = os.environ.get("TOGETHER_API_KEY")167 if tg_key:168 try:169 from openai import OpenAI170 self.providers["together"] = {171 "client": OpenAI(api_key=tg_key, base_url="https://api.together.xyz/v1"),172 "models": [173 "meta-llama/Llama-3-70b-chat-hf",174 "mistralai/Mixtral-8x7B-Instruct-v0.1",175 "Qwen/Qwen2.5-72B-Instruct",176 ],177 "priority": 4,178 }179 if not self.available:180 self.available = True181 except Exception:182 pass183 184 def _init_perplexity(self):185 """初始化Perplexity"""186 px_key = os.environ.get("PERPLEXITY_API_KEY")187 if px_key:188 try:189 from openai import OpenAI190 self.providers["perplexity"] = {191 "client": OpenAI(api_key=px_key, base_url="https://api.perplexity.ai"),192 "models": ["sonar", "sonar-pro"],193 "priority": 5,194 }195 if not self.available:196 self.available = True197 except Exception:198 pass199 200 def _init_groq(self):201 """初始化Groq"""202 gx_key = os.environ.get("GROQ_API_KEY")203 if gx_key:204 try:205 from openai import OpenAI206 self.providers["groq"] = {207 "client": OpenAI(api_key=gx_key, base_url="https://api.groq.com/openai/v1"),208 "models": [209 "llama-3.1-70b-versatile",210 "mixtral-8x7b-32768",211 "gemma2-9b-it",212 ],213 "priority": 6,214 }215 if not self.available:216 self.available = True217 except Exception:218 pass219 220 def _init_cc_switch(self):221 """初始化 CC Switch 本地代理 — 路由Claude/Gemini/Codex,最高优先级"""222 cc_url = os.environ.get("CC_SWITCH_BASE_URL", "http://127.0.0.1:15721/v1")223 try:224 import requests225 # 探针:检查 CC Switch 是否在线226 probe = requests.get(cc_url.replace("/v1", "/"), timeout=3)227 if probe.status_code in (200, 404): # 404 说明服务存在但路径不同,仍算在线228 from openai import OpenAI229 self.providers["cc_switch"] = {230 "client": OpenAI(231 api_key="cc-switch-local", # 本地代理无需真实Key232 base_url=cc_url,233 ),234 "models": [235 "claude-sonnet-4-5",236 "claude-3-5-haiku-20241022",237 "gemini-2.0-flash",238 "gemini-1.5-pro",239 ],240 "priority": -1, # ★ 最高优先级241 "name": "CC Switch",242 }243 self.available = True244 except Exception:245 pass # CC Switch不在线,自动降级到下一个供应商246 247 def _init_ollama(self):248 """初始化Ollama本地模型 — 0延迟0费用"""249 try:250 import requests251 resp = requests.get("http://localhost:11434/api/tags", timeout=5)252 if resp.status_code == 200:253 models = resp.json().get("models", [])254 if models:255 model_names = [m.get("name", "") for m in models]256 self.providers["ollama"] = {257 "client": requests.Session(),258 "base_url": "http://localhost:11434",259 "models": model_names,260 "priority": 1, # 本地最快最便宜261 }262 if not self.available:263 self.available = True264 except Exception:265 pass266 267 def _init_huggingface(self):268 """初始化HuggingFace"""269 hf_token = os.environ.get("HF_TOKEN")270 if hf_token:271 try:272 from huggingface_hub import InferenceClient273 self.providers["huggingface"] = {274 "client": InferenceClient(token=hf_token),275 "models": ["meta-llama/Llama-3.3-70B-Instruct"],276 "priority": 8,277 }278 if not self.available:279 self.available = True280 except Exception:281 pass282 283 def _init_deeprouter(self):284 """初始化DeepRouter"""285 for i in range(1, 5):286 key = os.environ.get(f"DEEPRouter_API_KEY_{i}")287 if key:288 try:289 from openai import OpenAI290 self.providers["deeprouter"] = {291 "client": OpenAI(api_key=key, base_url="https://www.deeprouter.top/v1"),292 "models": ["gpt-4o-mini", "gpt-3.5-turbo", "claude-3-haiku-20240307"],293 "priority": 2, # 仅次于OpenRouter294 }295 if not self.available:296 self.available = True297 break298 except Exception:299 continue300 301 def _init_sensenova(self):302 """初始化SenseNova"""303 for i in range(1, 5):304 key = os.environ.get(f"SENSENOVA_API_KEY_{i}")305 if key:306 try:307 from openai import OpenAI308 self.providers["sensenova"] = {309 "client": OpenAI(api_key=key, base_url="https://api.sensenova.cn/v1"),310 "models": ["yi-lightning", "yi-large"],311 "priority": 5,312 }313 if not self.available:314 self.available = True315 break316 except Exception:317 pass318 319 def _build_provider_list(self):320 """按优先级排序可用供应商"""321 self.provider_list = sorted(322 self.providers.values(),323 key=lambda p: p.get("priority", 999)324 )325 326 def generate(self, prompt: str, system_prompt: Optional[str] = None,327 max_tokens: int = 4000, timeout: int = 60) -> Optional[str]:328 """329 尝试所有可用供应商,按优先级返回第一个成功的结果。330 331 Args:332 prompt: 用户提示333 system_prompt: 系统提示334 max_tokens: 最大token数335 timeout: 超时秒数336 337 Returns:338 生成的文本,所有供应商都失败返回None339 """340 start_time = time.time()341 errors = []342 343 for provider_name, provider in enumerate(self.provider_list):344 pname = list(self.providers.keys())[provider_name]345 346 try:347 result = self._call_provider(pname, provider, prompt, system_prompt, max_tokens, timeout)348 if result:349 elapsed = time.time() - start_time350 self.active_provider = pname351 self.call_log.append({352 "provider": pname,353 "success": True,354 "elapsed": round(elapsed, 2),355 "timestamp": datetime.now().isoformat(),356 })357 return result358 except Exception as e:359 errors.append(f"{pname}: {str(e)[:100]}")360 361 # 所有供应商都失败362 self.call_log.append({363 "provider": "all_failed",364 "success": False,365 "errors": errors,366 "timestamp": datetime.now().isoformat(),367 })368 return None369 370 def _call_openrouter(self, provider, prompt, system_prompt, max_tokens):371 """调用OpenRouter"""372 client = provider["client"]373 messages = []374 if system_prompt:375 messages.append({"role": "system", "content": system_prompt})376 messages.append({"role": "user", "content": prompt})377 378 for model in provider.get("models", []):379 try:380 resp = client.chat.completions.create(381 model=model,382 messages=messages,383 temperature=0.7,384 max_tokens=max_tokens,385 timeout=30,386 )387 return resp.choices[0].message.content388 except Exception:389 continue390 return None391 392 def _call_gemini(self, provider, prompt, system_prompt, max_tokens):393 """调用Gemini"""394 gc = provider["client"]395 if gc.available:396 return gc.generate_content(prompt, system_prompt)397 return None398 399 def _call_deepseek(self, provider, prompt, system_prompt, max_tokens):400 """调用DeepSeek"""401 client = provider["client"]402 messages = []403 if system_prompt:404 messages.append({"role": "system", "content": system_prompt})405 messages.append({"role": "user", "content": prompt})406 407 for model in provider.get("models", []):408 try:409 resp = client.chat.completions.create(410 model=model,411 messages=messages,412 temperature=0.7,413 max_tokens=max_tokens,414 )415 return resp.choices[0].message.content416 except Exception:417 continue418 return None419 420 def _call_together(self, provider, prompt, system_prompt, max_tokens):421 """调用Together AI"""422 client = provider["client"]423 messages = []424 if system_prompt:425 messages.append({"role": "system", "content": system_prompt})426 messages.append({"role": "user", "content": prompt})427 428 for model in provider.get("models", []):429 try:430 resp = client.chat.completions.create(431 model=model,432 messages=messages,433 temperature=0.7,434 max_tokens=max_tokens,435 )436 return resp.choices[0].message.content437 except Exception:438 continue439 return None440 441 def _call_ollama(self, provider, prompt, system_prompt, max_tokens):442 """调用Ollama本地模型"""443 client = provider["client"]444 base_url = provider.get("base_url", "http://localhost:11434")445 messages = []446 if system_prompt:447 messages.append({"role": "system", "content": system_prompt})448 messages.append({"role": "user", "content": prompt})449 450 # 优先尝试gemma2(content字段正常返回),gemma4的thinking模式导致content为空451 models = sorted(provider.get("models", []), key=lambda m: 0 if "gemma2" in m else 1)452 453 for model in models:454 try:455 resp = client.post(f"{base_url}/api/chat", json={456 "model": model,457 "messages": messages,458 "stream": False,459 "options": {"num_predict": max_tokens, "num_ctx": 4096},460 }, timeout=120)461 if resp.status_code == 200:462 content = resp.json().get("message", {}).get("content", "").strip()463 if content:464 return content465 except Exception:466 continue467 return None468 469 def _call_deeprouter(self, provider, prompt, system_prompt, max_tokens):470 """调用DeepRouter"""471 client = provider["client"]472 messages = []473 if system_prompt:474 messages.append({"role": "system", "content": system_prompt})475 messages.append({"role": "user", "content": prompt})476 477 for model in provider.get("models", []):478 try:479 resp = client.chat.completions.create(480 model=model,481 messages=messages,482 temperature=0.7,483 max_tokens=max_tokens,484 )485 return resp.choices[0].message.content486 except Exception:487 continue488 return None489 490 def _call_sensenova(self, provider, prompt, system_prompt, max_tokens):491 """调用SenseNova"""492 client = provider["client"]493 messages = []494 if system_prompt:495 messages.append({"role": "system", "content": system_prompt})496 messages.append({"role": "user", "content": prompt})497 498 for model in provider.get("models", []):499 try:500 resp = client.chat.completions.create(501 model=model,502 messages=messages,503 temperature=0.7,504 max_tokens=max_tokens,505 )506 return resp.choices[0].message.content507 except Exception:508 continue509 return None510 511 def _call_provider(self, name, provider, prompt, system_prompt, max_tokens, timeout):512 """分发到具体供应商"""513 if name == "openrouter":514 return self._call_openrouter(provider, prompt, system_prompt, max_tokens)515 elif name == "deeprouter":516 return self._call_deeprouter(provider, prompt, system_prompt, max_tokens)517 elif name == "sensenova":518 return self._call_sensenova(provider, prompt, system_prompt, max_tokens)519 elif name == "gemini":520 return self._call_gemini(provider, prompt, system_prompt, max_tokens)521 elif name == "deepseek":522 return self._call_deepseek(provider, prompt, system_prompt, max_tokens)523 elif name == "together":524 return self._call_together(provider, prompt, system_prompt, max_tokens)525 elif name == "ollama":526 return self._call_ollama(provider, prompt, system_prompt, max_tokens)527 return None528 529 def get_status(self) -> Dict:530 """获取引擎状态"""531 status = {532 "available": self.available,533 "active_provider": self.active_provider,534 "providers_configured": list(self.providers.keys()),535 "call_log_last": self.call_log[-3:] if self.call_log else [],536 }537 return status538 539 def __repr__(self):540 return f"<LLMEngine(active: {self.active_provider}, providers: {list(self.providers.keys())})>"541 