mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 20:51:53 +00:00
Demande du référent : la génération opt-in devient réellement configurable. AI_GENERATION=off|api, AI_API_KEY (exigée en mode api — le boot refuse sinon, jamais loguée, /healthz n'expose que le mode), AI_MODEL (défaut claude-opus-4-8). generation.py : interface Generateur — GenerateurExtractif (contrat de base sans LLM) et GenerateurAPI (SDK officiel anthropic, dépendance optionnelle --extra generation, absente des tests/CI). Consigne : citations [n] obligatoires depuis les extraits anonymisés, jamais d'invention, rappel de validation humaine. Tout échec (refus du modèle, quota, réseau) retombe silencieusement sur l'extractif. 5 tests ajoutés (19 pytest) + .env.example. Vérifié en réel : boot refusé api-sans-clé, générateur construit, healthz sans secret. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
"""ADR-004 §3 — la génération est OPT-IN et configurable : clé, modèle, mode.
|
|
Sans clé, le mode extractif est le contrat ; en mode api sans clé, le boot
|
|
refuse (config validée au démarrage, comme l'API NestJS)."""
|
|
|
|
import pytest
|
|
|
|
from siop_ai.config import charger_reglages
|
|
from siop_ai.generation import GenerateurExtractif, construire_generateur, construire_invite
|
|
from siop_ai.recherche import ExtraitTrouve
|
|
|
|
|
|
def _extrait(titre: str, locator: str, content: str) -> ExtraitTrouve:
|
|
return ExtraitTrouve(
|
|
source_type="DOCUMENT",
|
|
document_id="d-1",
|
|
work_order_id=None,
|
|
titre=titre,
|
|
locator=locator,
|
|
content=content,
|
|
score=0.9,
|
|
)
|
|
|
|
|
|
def test_defaut_extractif_sans_cle():
|
|
generateur = construire_generateur("off", "", "claude-opus-4-8")
|
|
assert isinstance(generateur, GenerateurExtractif)
|
|
assert generateur.rediger("couple de serrage ?", [_extrait("n", "p. 1", "x")]) is None
|
|
|
|
|
|
def test_mode_api_sans_cle_refuse_au_boot(monkeypatch):
|
|
monkeypatch.setenv("AI_GENERATION", "api")
|
|
monkeypatch.delenv("AI_API_KEY", raising=False)
|
|
with pytest.raises(ValueError, match="AI_API_KEY"):
|
|
charger_reglages()
|
|
|
|
|
|
def test_mode_inconnu_refuse_au_boot(monkeypatch):
|
|
monkeypatch.setenv("AI_GENERATION", "toujours")
|
|
with pytest.raises(ValueError, match="AI_GENERATION"):
|
|
charger_reglages()
|
|
|
|
|
|
def test_cle_et_modele_configurables(monkeypatch):
|
|
monkeypatch.setenv("AI_GENERATION", "api")
|
|
monkeypatch.setenv("AI_API_KEY", "sk-test-123")
|
|
monkeypatch.setenv("AI_MODEL", "claude-opus-4-8")
|
|
reglages = charger_reglages()
|
|
assert reglages.ai_api_key == "sk-test-123"
|
|
assert reglages.ai_model == "claude-opus-4-8"
|
|
|
|
|
|
def test_invite_numerote_les_extraits_et_rien_d_autre():
|
|
invite = construire_invite(
|
|
"quel couple de serrage pour les guides ?",
|
|
[
|
|
_extrait("Notice Gen2.pdf", "p. 42", "Serrer à 25 N·m."),
|
|
_extrait("OT-2026-0341", "bilan du 2026-07-17", "Coulisseaux remplacés."),
|
|
],
|
|
)
|
|
assert "[1] Notice Gen2.pdf · p. 42" in invite
|
|
assert "[2] OT-2026-0341 · bilan du 2026-07-17" in invite
|
|
assert invite.endswith("Question : quel couple de serrage pour les guides ?")
|