chore: post-deployment cleanup and docs update
- Make AI routing rules DB-driven (read from system_configs, removed from config.py) - Add translation quota tracking to LLM translation (OpenAIProvider) - Add Alibaba MT ECS RAM role support (STS token, no AccessKey needed) - Fix admin sidebar link for AI模型配置 page - Fix Quota.vue API path (quotas → translation-quotas) - Fix login auto-redirect to dashboard - Add provider dropdown selects to AI routing config UI - Clean up stale ai_provider_* system_configs records - Remove OpencodeGo, Spark providers (code + DB) - Update deploy config: nginx port 8000, systemd cwd
This commit is contained in:
+59
-30
@@ -1,17 +1,26 @@
|
||||
from typing import Dict, Any, Optional, List
|
||||
from app.ai.base import AIProvider
|
||||
from app.ai.providers import SparkProvider, SensenovaProvider, OpencodeGoProvider, NvidiaProvider, AlibabaMTProvider
|
||||
from app.config import settings
|
||||
from app.ai.providers import SensenovaProvider, NvidiaProvider, AlibabaMTProvider
|
||||
from app.ai.trade_corpus import TradeCorpus
|
||||
from app.config import settings
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_ROUTING: Dict[str, dict] = {
|
||||
"translate": {"primary": "sensenova", "fallback": ["alibaba-mt", "nvidia"]},
|
||||
"reply": {"primary": "sensenova", "fallback": ["nvidia"]},
|
||||
"marketing": {"primary": "sensenova", "fallback": ["nvidia"]},
|
||||
"extract": {"primary": "sensenova", "fallback": ["nvidia"]},
|
||||
"quotation": {"primary": "sensenova", "fallback": ["nvidia"]},
|
||||
"chat": {"primary": "sensenova", "fallback": ["nvidia"]},
|
||||
}
|
||||
|
||||
|
||||
class AIRouter:
|
||||
def __init__(self):
|
||||
self.providers: Dict[str, AIProvider] = {}
|
||||
self.routing_rules = settings.AI_ROUTING
|
||||
self.routing_rules = dict(DEFAULT_ROUTING)
|
||||
self.corpus = TradeCorpus()
|
||||
|
||||
async def reload_from_db(self, db_session) -> int:
|
||||
@@ -38,8 +47,47 @@ class AIRouter:
|
||||
else:
|
||||
logger.warning("No enabled AI providers found in DB")
|
||||
|
||||
await self._load_routing_rules(db_session)
|
||||
return len(rows)
|
||||
|
||||
async def _load_routing_rules(self, db_session):
|
||||
from app.models.system_config import SystemConfig
|
||||
from sqlalchemy import select
|
||||
|
||||
# Try consolidated key first
|
||||
result = await db_session.execute(
|
||||
select(SystemConfig).where(SystemConfig.key == "ai_routing")
|
||||
)
|
||||
cfg = result.scalar_one_or_none()
|
||||
if cfg and isinstance(cfg.value, dict):
|
||||
self.routing_rules = {**DEFAULT_ROUTING, **cfg.value}
|
||||
logger.info("Loaded routing rules from system_configs (ai_routing)")
|
||||
return
|
||||
|
||||
# Fallback: load individual per-task keys
|
||||
task_keys = {
|
||||
"translate": "ai_provider_translate",
|
||||
"reply": "ai_provider_reply",
|
||||
"marketing": "ai_provider_marketing",
|
||||
"extract": "ai_provider_extract",
|
||||
"quotation": "ai_provider_quotation",
|
||||
}
|
||||
loaded = {}
|
||||
for task, key in task_keys.items():
|
||||
result = await db_session.execute(
|
||||
select(SystemConfig).where(SystemConfig.key == key)
|
||||
)
|
||||
cfg = result.scalar_one_or_none()
|
||||
if cfg and isinstance(cfg.value, dict):
|
||||
loaded[task] = cfg.value
|
||||
|
||||
if loaded:
|
||||
self.routing_rules = {**DEFAULT_ROUTING, **loaded}
|
||||
logger.info(f"Loaded routing rules from system_configs (individual keys): {list(loaded.keys())}")
|
||||
else:
|
||||
self.routing_rules = dict(DEFAULT_ROUTING)
|
||||
logger.info("No routing rules in system_configs, using defaults")
|
||||
|
||||
async def seed_from_env(self, db_session) -> int:
|
||||
from app.models.ai_provider import AIProvider
|
||||
|
||||
@@ -53,34 +101,19 @@ class AIRouter:
|
||||
base_url=settings.SENSENOVA_BASE_URL,
|
||||
model_name=settings.SENSENOVA_MODEL, priority=0, enabled=True,
|
||||
))
|
||||
if settings.OPENCODE_GO_API_KEY:
|
||||
seeds.append(AIProvider(
|
||||
name="OpencodeGo", provider_type="opencode_go",
|
||||
api_key=settings.OPENCODE_GO_API_KEY,
|
||||
base_url=settings.OPENCODE_GO_BASE_URL,
|
||||
model_name=settings.OPENCODE_GO_MODEL, priority=1, enabled=True,
|
||||
))
|
||||
if settings.NVIDIA_API_KEY:
|
||||
seeds.append(AIProvider(
|
||||
name="NVIDIA", provider_type="nvidia",
|
||||
api_key=settings.NVIDIA_API_KEY,
|
||||
base_url=settings.NVIDIA_BASE_URL,
|
||||
model_name=settings.NVIDIA_MODEL, priority=2, enabled=True,
|
||||
))
|
||||
if settings.IFLYTEK_API_KEY:
|
||||
seeds.append(AIProvider(
|
||||
name="讯飞 Spark", provider_type="spark",
|
||||
api_key=settings.IFLYTEK_API_KEY,
|
||||
base_url=settings.IFLYTEK_API_BASE,
|
||||
model_name=settings.IFLYTEK_MODEL, priority=3, enabled=True,
|
||||
))
|
||||
if settings.ALIBABA_ACCESS_KEY_ID and settings.ALIBABA_ACCESS_KEY_SECRET:
|
||||
seeds.append(AIProvider(
|
||||
name="阿里翻译", provider_type="alibaba-mt",
|
||||
api_key=settings.ALIBABA_ACCESS_KEY_ID,
|
||||
api_secret=settings.ALIBABA_ACCESS_KEY_SECRET,
|
||||
model_name="alibaba-mt", priority=4, enabled=True,
|
||||
model_name=settings.NVIDIA_MODEL, priority=1, enabled=True,
|
||||
))
|
||||
seeds.append(AIProvider(
|
||||
name="阿里翻译", provider_type="alibaba-mt",
|
||||
api_key=settings.ALIBABA_ACCESS_KEY_ID or "",
|
||||
api_secret=settings.ALIBABA_ACCESS_KEY_SECRET or "",
|
||||
model_name="alibaba-mt", priority=3, enabled=True,
|
||||
))
|
||||
|
||||
for p in seeds:
|
||||
db_session.add(p)
|
||||
@@ -99,12 +132,8 @@ class AIRouter:
|
||||
t = p.provider_type
|
||||
if t == "sensenova":
|
||||
return SensenovaProvider(api_key=p.api_key, model=p.model_name, base_url=p.base_url)
|
||||
elif t == "opencode_go":
|
||||
return OpencodeGoProvider(api_key=p.api_key, model=p.model_name, base_url=p.base_url)
|
||||
elif t == "nvidia":
|
||||
return NvidiaProvider(api_key=p.api_key, model=p.model_name, base_url=p.base_url)
|
||||
elif t == "spark":
|
||||
return SparkProvider(api_key=p.api_key, model=p.model_name, base_url=p.base_url)
|
||||
elif t == "alibaba-mt":
|
||||
return AlibabaMTProvider(access_key_id=p.api_key, access_key_secret=p.api_secret or "")
|
||||
else:
|
||||
@@ -117,7 +146,7 @@ class AIRouter:
|
||||
def get_providers_for_task(self, task_type: str) -> List[AIProvider]:
|
||||
rules = self.routing_rules.get(
|
||||
task_type,
|
||||
{"primary": "sensenova", "fallback": ["opencode_go"]},
|
||||
{"primary": "sensenova", "fallback": ["nvidia"]},
|
||||
)
|
||||
ordered = []
|
||||
seen = set()
|
||||
|
||||
Reference in New Issue
Block a user