mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 07:19:35 +00:00
- Add integrations CRM and AI routing APIs; Salesforce OAuth refresh; lead CRM metadata - Marketer hub, settings CRM UI, OS views; premium landing and strategy_summary differentiators - Docs: API-MAP, product guide, competitive matrix, launch simulation, AGENT-MAP LLM routing - Sync script: strategy legal + competitive matrix to public; pytest DB isolation (.pytest_dealix.sqlite) - Tests: CRM status and AI routing smoke; check_go_live_gate UTF-8 stdout on Windows - Alembic migrations for strategic deal links and lead company/sector/city Made-with: Cursor
117 lines
4.0 KiB
Python
117 lines
4.0 KiB
Python
"""Tenant-level LLM routing policy (no API keys exposed)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, Literal
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.api.deps import get_current_user, require_role
|
|
from app.config import get_settings
|
|
from app.database import get_db
|
|
from app.models.tenant import Tenant
|
|
from app.models.user import User
|
|
|
|
router = APIRouter(prefix="/ai", tags=["AI — routing"])
|
|
|
|
TaskKey = Literal["discovery", "negotiation", "compliance", "strategy_summary", "embeddings"]
|
|
|
|
|
|
class TaskRoute(BaseModel):
|
|
provider: str = Field(..., description="groq | openai | anthropic | etc.")
|
|
model: str = Field(..., description="Model id for that provider")
|
|
|
|
|
|
class RoutingMap(BaseModel):
|
|
discovery: TaskRoute | None = None
|
|
negotiation: TaskRoute | None = None
|
|
compliance: TaskRoute | None = None
|
|
strategy_summary: TaskRoute | None = None
|
|
embeddings: TaskRoute | None = None
|
|
|
|
|
|
def _defaults_from_settings() -> Dict[str, Dict[str, str]]:
|
|
s = get_settings()
|
|
primary = (s.LLM_PRIMARY_PROVIDER or "groq").lower()
|
|
if primary == "openai":
|
|
default_model = s.OPENAI_MODEL
|
|
else:
|
|
default_model = s.GROQ_MODEL
|
|
return {
|
|
"discovery": {"provider": primary, "model": default_model},
|
|
"negotiation": {"provider": primary, "model": default_model},
|
|
"compliance": {"provider": primary, "model": s.OPENAI_MINI_MODEL if primary == "openai" else s.GROQ_FAST_MODEL},
|
|
"strategy_summary": {"provider": primary, "model": default_model},
|
|
"embeddings": {"provider": "openai", "model": s.EMBEDDING_MODEL},
|
|
}
|
|
|
|
|
|
def _available_providers() -> list[str]:
|
|
s = get_settings()
|
|
out = []
|
|
if s.GROQ_API_KEY:
|
|
out.append("groq")
|
|
if s.OPENAI_API_KEY:
|
|
out.append("openai")
|
|
if s.ANTHROPIC_API_KEY:
|
|
out.append("anthropic")
|
|
if s.DEEPSEEK_API_KEY:
|
|
out.append("deepseek")
|
|
if s.GOOGLE_API_KEY:
|
|
out.append("google")
|
|
if s.ZAI_API_KEY:
|
|
out.append("zai")
|
|
return out
|
|
|
|
|
|
def _merge_routing(tenant_settings: dict | None) -> Dict[str, Dict[str, str]]:
|
|
base = _defaults_from_settings()
|
|
custom = (tenant_settings or {}).get("llm_routing") or {}
|
|
for k, v in custom.items():
|
|
if isinstance(v, dict) and v.get("provider") and v.get("model"):
|
|
base[k] = {"provider": str(v["provider"]), "model": str(v["model"])}
|
|
return base
|
|
|
|
|
|
@router.get("/routing")
|
|
async def get_ai_routing(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
r = await db.execute(select(Tenant).where(Tenant.id == current_user.tenant_id))
|
|
tenant = r.scalar_one_or_none()
|
|
if not tenant:
|
|
raise HTTPException(status_code=404, detail="Tenant not found")
|
|
return {
|
|
"effective": _merge_routing(tenant.settings),
|
|
"available_providers": _available_providers(),
|
|
"note_ar": "المفاتيح تبقى في الخادم فقط — الواجهة ترى أسماء المزودين والنماذج فقط.",
|
|
}
|
|
|
|
|
|
@router.put("/routing", dependencies=[Depends(require_role("owner", "manager", "admin"))])
|
|
async def put_ai_routing(
|
|
body: RoutingMap,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
r = await db.execute(select(Tenant).where(Tenant.id == current_user.tenant_id))
|
|
tenant = r.scalar_one_or_none()
|
|
if not tenant:
|
|
raise HTTPException(status_code=404, detail="Tenant not found")
|
|
patch: Dict[str, Any] = {}
|
|
data = body.model_dump(exclude_none=True)
|
|
for task, spec in data.items():
|
|
if isinstance(spec, dict):
|
|
patch[task] = spec
|
|
base = dict(tenant.settings or {})
|
|
lr = dict(base.get("llm_routing") or {})
|
|
lr.update(patch)
|
|
base["llm_routing"] = lr
|
|
tenant.settings = base
|
|
await db.flush()
|
|
return {"status": "ok", "effective": _merge_routing(base)}
|