fix(llm): Mistral 403 tier_not_allowed — défauts tous-plans + sonde chat + migration 36 (v7.59.1)
La clé fonctionne: /v1/models 200 + chat 200 (vérifié live). L'échec venait
du modèle: mistral-large-latest (défaut PROVIDERS + tête de liste) n'est pas
servi par les plans d'abonnement basiques → 403 code 1910 « tier_not_allowed »,
et le Test connection sélectionnait systématiquement ce premier candidat.
- PROVIDERS mistral default → mistral-small-latest (tous plans)
- PROVIDER_MODELS réordonnée: tous-plans d'abord (ordre du test de connexion)
- mistral ∈ _CHAT_VALIDATED_PROVIDERS: le fetch modèles ne garde que les
modèles réellement servis (46 listés → 25 utilisables avec la clé du compte)
- migration 36: default_model bloqué (large/pixtral-large) reset en base
Test live déployé: {ok:true, model:mistral-small-latest, reply:PONG,
verified:true} · tests/test_agent.py 53/53 · nouveau test d'ancrage
test_mistral_defaults_are_tier_safe
This commit is contained in:
@@ -1,5 +1,29 @@
|
|||||||
# Changelog - FlowDeck
|
# Changelog - FlowDeck
|
||||||
|
|
||||||
|
## v7.59.1 (2026-10-07) — Fix fournisseur Mistral (clé valide, 403 tier_not_allowed)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Mistral « Test connection » échouait malgré une clé valide** — cause
|
||||||
|
racine : `mistral-large-latest` (modèle par défaut + tête de liste) n'est
|
||||||
|
pas servi par les plans d'abonnement basiques → `403 tier_not_allowed`
|
||||||
|
(code 1910). La clé elle-même fonctionne (`/v1/models` 200, chat 200 sur
|
||||||
|
small/medium/nemo/codestral — vérifié en direct).
|
||||||
|
- `PROVIDERS["mistral"]` default → `mistral-small-latest` (tous plans).
|
||||||
|
- `PROVIDER_MODELS["mistral"]` réordonnée : modèles tous-plans d'abord
|
||||||
|
(le test de connexion sélectionne le premier candidat).
|
||||||
|
- `mistral` ajouté à `_CHAT_VALIDATED_PROVIDERS` : le fetch des modèles
|
||||||
|
sonde chat/completions et exclut les modèles hors plan (46 listés →
|
||||||
|
25 réellement utilisables avec la clé du compte).
|
||||||
|
- **Migration 36** : `default_model` des lignes mistral pointant sur un
|
||||||
|
modèle bloqué réinitialisé (→ nouveau défaut), `llm_config.model` idem.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- `tests/test_agent.py::test_mistral_defaults_are_tier_safe` · suite agent
|
||||||
|
OK · test de connexion live sur l'instance déployée : `{"ok":true,
|
||||||
|
"model":"mistral-small-latest","reply":"PONG","verified":true}`.
|
||||||
|
|
||||||
## v7.59.0 (2026-10-07) — Mobile : drawer réglable, side-nav settings, aide refondue
|
## v7.59.0 (2026-10-07) — Mobile : drawer réglable, side-nav settings, aide refondue
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
@@ -186,7 +186,7 @@ async def lifespan(_app: FastAPI):
|
|||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="FlowDeck",
|
title="FlowDeck",
|
||||||
version="7.59.0",
|
version="7.59.1",
|
||||||
docs_url="/docs",
|
docs_url="/docs",
|
||||||
redoc_url="/redoc",
|
redoc_url="/redoc",
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
|
|||||||
@@ -1678,3 +1678,24 @@ def _migration_agent_memory(conn: sqlite3.Connection) -> None:
|
|||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
)"""
|
)"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register(36, "v7.59.1: mistral — modèles par défaut hors plan basique")
|
||||||
|
def _migration_mistral_default_model(conn: sqlite3.Connection) -> None:
|
||||||
|
"""mistral-large-latest / pixtral-large-latest renvoient 403
|
||||||
|
« tier_not_allowed » sur les plans d'abonnement basiques : la clé est
|
||||||
|
valide mais le test de connexion et l'agent échouaient. Reset du
|
||||||
|
default_model stocké → vide = nouveau défaut du provider (small-latest,
|
||||||
|
servi par tous les plans).
|
||||||
|
"""
|
||||||
|
blocked = ("mistral-large-latest", "pixtral-large-latest")
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE user_llm_keys SET default_model='' "
|
||||||
|
"WHERE provider='mistral' AND default_model IN (?,?)",
|
||||||
|
blocked,
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE llm_config SET model='mistral-small-latest' "
|
||||||
|
"WHERE provider='mistral' AND model IN (?,?)",
|
||||||
|
blocked,
|
||||||
|
)
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ logger = logging.getLogger(__name__)
|
|||||||
PROVIDERS = {
|
PROVIDERS = {
|
||||||
"openai": ("https://api.openai.com/v1", "gpt-4o"),
|
"openai": ("https://api.openai.com/v1", "gpt-4o"),
|
||||||
"anthropic": ("https://api.anthropic.com/v1", "claude-opus-4-8"),
|
"anthropic": ("https://api.anthropic.com/v1", "claude-opus-4-8"),
|
||||||
"mistral": ("https://api.mistral.ai/v1", "mistral-large-latest"),
|
# mistral-small est servi par tous les plans d'abonnement ;
|
||||||
|
# mistral-large → 403 tier_not_allowed sur les plans basiques.
|
||||||
|
"mistral": ("https://api.mistral.ai/v1", "mistral-small-latest"),
|
||||||
"cohere": ("https://api.cohere.ai/compatibility/v1", "command-a-plus-05-2026"),
|
"cohere": ("https://api.cohere.ai/compatibility/v1", "command-a-plus-05-2026"),
|
||||||
"google": ("https://generativelanguage.googleapis.com/v1beta/openai", "gemini-2.0-flash"),
|
"google": ("https://generativelanguage.googleapis.com/v1beta/openai", "gemini-2.0-flash"),
|
||||||
"groq": ("https://api.groq.com/openai/v1", "llama-3.3-70b-versatile"),
|
"groq": ("https://api.groq.com/openai/v1", "llama-3.3-70b-versatile"),
|
||||||
@@ -94,8 +96,11 @@ PROVIDER_LABELS: dict[str, str] = {
|
|||||||
PROVIDER_MODELS: dict[str, list[str]] = {
|
PROVIDER_MODELS: dict[str, list[str]] = {
|
||||||
"openai": ["gpt-4o", "gpt-4o-mini", "gpt-4.1", "gpt-4.1-mini", "o3-mini", "gpt-4-turbo"],
|
"openai": ["gpt-4o", "gpt-4o-mini", "gpt-4.1", "gpt-4.1-mini", "o3-mini", "gpt-4-turbo"],
|
||||||
"anthropic": ["claude-opus-4-8", "claude-sonnet-4-5", "claude-3-5-sonnet", "claude-haiku-4-5"],
|
"anthropic": ["claude-opus-4-8", "claude-sonnet-4-5", "claude-3-5-sonnet", "claude-haiku-4-5"],
|
||||||
"mistral": ["mistral-large-latest", "mistral-medium-latest", "mistral-small-latest",
|
# Ordre = ordre de sélection du test de connexion : les modèles servis par
|
||||||
"codestral-latest", "open-mistral-nemo", "pixtral-large-latest"],
|
# tous les plans d'abord (large/pixtral-large = 403 tier_not_allowed en plan
|
||||||
|
# basique), les modèles à plan élevé en fin de liste.
|
||||||
|
"mistral": ["mistral-small-latest", "mistral-medium-latest", "open-mistral-nemo",
|
||||||
|
"codestral-latest", "mistral-large-latest", "pixtral-large-latest"],
|
||||||
"cohere": ["command-a-plus-05-2026", "command-r-plus", "command-r", "command-a-03-2025"],
|
"cohere": ["command-a-plus-05-2026", "command-r-plus", "command-r", "command-a-03-2025"],
|
||||||
"google": ["gemini-2.0-flash", "gemini-2.0-flash-lite", "gemini-1.5-pro", "gemini-1.5-flash"],
|
"google": ["gemini-2.0-flash", "gemini-2.0-flash-lite", "gemini-1.5-pro", "gemini-1.5-flash"],
|
||||||
"groq": ["llama-3.3-70b-versatile", "llama-3.1-8b-instant",
|
"groq": ["llama-3.3-70b-versatile", "llama-3.1-8b-instant",
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ from app.services.llm_client import PROVIDER_LABELS, PROVIDER_MODELS, PROVIDERS
|
|||||||
# before being exposed as "usable models". NVIDIA exposes all of its catalog
|
# before being exposed as "usable models". NVIDIA exposes all of its catalog
|
||||||
# (embeddings, rerank, image/video/audio gen…) many of which answer 404 on
|
# (embeddings, rerank, image/video/audio gen…) many of which answer 404 on
|
||||||
# chat completions — the exact failure the user hit.
|
# chat completions — the exact failure the user hit.
|
||||||
_CHAT_VALIDATED_PROVIDERS = frozenset({"nvidia"})
|
# Mistral : le /models liste des modèles hors du plan d'abonnement du compte
|
||||||
|
# (403 « tier_not_allowed » ex. mistral-large sur plan basique) — la sonde
|
||||||
|
# chat ne garde que ceux réellement servis par la CLÉ de l'utilisateur.
|
||||||
|
_CHAT_VALIDATED_PROVIDERS = frozenset({"nvidia", "mistral"})
|
||||||
|
|
||||||
# Markers that identify clearly non-chat models (embeddings, rerank, media gen…).
|
# Markers that identify clearly non-chat models (embeddings, rerank, media gen…).
|
||||||
_NON_CHAT_MARKERS = (
|
_NON_CHAT_MARKERS = (
|
||||||
|
|||||||
@@ -89,6 +89,21 @@ def test_llm_client_providers_listed(client):
|
|||||||
assert name in PROVIDERS
|
assert name in PROVIDERS
|
||||||
|
|
||||||
|
|
||||||
|
def test_mistral_defaults_are_tier_safe(client):
|
||||||
|
"""La clé Mistral plan basique → 403 « tier_not_allowed » sur mistral-large/pixtral-large.
|
||||||
|
Le modèle par défaut et le premier de la liste du test de connexion doivent
|
||||||
|
être des modèles servis par tous les plans."""
|
||||||
|
from app.services.llm_client import PROVIDERS, PROVIDER_MODELS
|
||||||
|
tier_blocked = {"mistral-large-latest", "pixtral-large-latest"}
|
||||||
|
assert PROVIDERS["mistral"][1] not in tier_blocked
|
||||||
|
assert PROVIDER_MODELS["mistral"][0] not in tier_blocked
|
||||||
|
# le fallback du modèle retiré (PROVIDERS default) est présent dans la liste
|
||||||
|
assert PROVIDERS["mistral"][1] in PROVIDER_MODELS["mistral"]
|
||||||
|
# et la liste des modèles Mistral est validée chat à chaque fetch (403 exclus)
|
||||||
|
from app.services.llm_config import _CHAT_VALIDATED_PROVIDERS
|
||||||
|
assert "mistral" in _CHAT_VALIDATED_PROVIDERS
|
||||||
|
|
||||||
|
|
||||||
def test_llm_client_openai_compatible_bases(client):
|
def test_llm_client_openai_compatible_bases(client):
|
||||||
"""Providers that need an OpenAI-compatible surface must point at it.
|
"""Providers that need an OpenAI-compatible surface must point at it.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user