feat(#875): LLM Router shadow mode + consolidate litellm master key

This commit is contained in:
QihuAI Design committed 2026-08-04 13:16:30 +08:00
1 parent 037c965c26
commit 180409d317
3 files changed
+390 -5

No files matched your search

+28 -1
View File
@@ -193,6 +193,23 @@ from ..middleware.log_service import LogService
from ..capability_registry import resolve_auto_model
try:
from ..core.llm_router import (
resolve_api_key as _llm_resolve_api_key,
route as _llm_route,
set_route_ctx as _set_route_ctx,
SHADOW as _LLM_ROUTER_SHADOW,
)
except Exception as _llm_router_err:
import os as _os, logging as _logging
_logging.getLogger(__name__).warning(f"[chat] llm_router import failed, router disabled: {_llm_router_err}")
def _llm_resolve_api_key(api_url, model_api_key, provider_api_key):
if api_url and "qihu-aiflow-litellm" in api_url:
return _os.environ.get("LITELLM_MASTER_KEY") or "sk-litellm-master-key-aiflow-2026"
return model_api_key or provider_api_key
_llm_route = None
_set_route_ctx = None
_LLM_ROUTER_SHADOW = True
@@ -2820,6 +2837,16 @@ async def _send_message_core(
_try_models.extend([m for m in _fallbacks if m not in _try_models])
# ===== LLM Router(#875 影子模式)=====
try:
if _llm_route is not None:
_set_route_ctx(tenant_id=getattr(current_user, "tenant_id", "") or "", claw_type="web")
_rm = _llm_route("text", db=db, legacy_primary=_try_models[0].name if _try_models else None)
if not _LLM_ROUTER_SHADOW and _rm.models:
_try_models = _rm.models
except Exception as _e:
logger.warning(f"[chat] LLM Router 观测失败(降级忽略): {_e}")
_retry_log = []
_all_failed = False
@@ -3082,8 +3109,8 @@ async def _send_message_core(
# 2026-07-29 fix: 调用 LiteLLM 端点时,必须用 master key (sk-litellm-...) 而非模型自身 key (tp-c...)
# LiteLLM 内置每个 model 的真实 key 在 litellm_config.yaml,chat 不应直接传模型的 key
api_key = _llm_resolve_api_key(api_url, api_key, provider.api_key if provider else None)
if api_url and "qihu-aiflow-litellm" in api_url:
api_key = os.environ.get("LITELLM_MASTER_KEY") or "sk-litellm-master-key-aiflow-2026"
auth_header = None # 用默认 Authorization: Bearer
logger.info("[chat] LiteLLM endpoint detected, using master key instead of model key")
+22 -4
View File
@@ -31,6 +31,13 @@ from sqlalchemy.orm import Session
from ..capability_registry import resolve_auto_model
from ..models import AIModel, ModelProvider, TokenUsageLog, gen_uuid
from app.core.observability import get_trace_id
# #875 LLM Router:统一模型路由层(影子模式先行,收敛 litellm 主密钥硬编码)
from ..core.llm_router import (
route as _llm_route,
set_route_ctx as _set_route_ctx,
SHADOW as _LLM_ROUTER_SHADOW,
resolve_api_key as _llm_resolve_api_key,
)
logger = logging.getLogger(__name__)
@@ -212,6 +219,19 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im
).order_by(AIModel.is_default.desc()).all()
_fallbacks = [m for m in _fallbacks if _is_text_only(m)]
_try_models.extend([m for m in _fallbacks[:4] if m not in _try_models])
# ===== LLM Router(#875 影子模式)=====
# 计算决策并打 claw_type 标签 metrics;影子模式下仅观测,不改流量。
# 切主路径:LLM_ROUTER_SHADOW=0 时改用 Router 候选链(已含文本过滤,等同此处逻辑)。
try:
_set_route_ctx(tenant_id=getattr(user, "tenant_id", "") or "", claw_type="im_wechat")
_rm = _llm_route("text", db=db, legacy_primary=_try_models[0].name if _try_models else None)
if not _LLM_ROUTER_SHADOW and _rm.models:
_try_models = _rm.models
_debug_log(f"ROUTER_LIVE models={[m.name for m in _try_models]}")
except Exception as _e:
logger.warning(f"[im_llm] LLM Router 观测失败(降级忽略): {_e}")
_debug_log(f"TRY_MODELS user={user_id} count={len(_try_models)} names={[m.name for m in _try_models]}")
if not _try_models:
@@ -268,10 +288,8 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im
provider = db.query(ModelProvider).filter(ModelProvider.id == ai_model.provider_id).first()
api_key = ai_model.api_key or (provider.api_key if provider else None)
api_url = ai_model.endpoint
# 2026-07-30: LiteLLM 端点统一用 master key(与 chat_service.py 对齐)
if api_url and "qihu-aiflow-litellm" in api_url:
_litellm_key = os.environ.get("LITELLM_MASTER_KEY") or "sk-litellm-master-key-aiflow-2026"
api_key = _litellm_key
# 2026-07-30: LiteLLM 端点统一用 master key(收敛进 LLM Router,两端唯一来源)
api_key = _llm_resolve_api_key(api_url, api_key, provider.api_key if provider else None)
if not api_url and provider and provider.api_base_url:
api_url = f"{provider.api_base_url.rstrip('/')}/v1/chat/completions"
if not (api_key and api_url):