File size: 5,331 Bytes
2ffc435 9e8fbdf 2ffc435 ef2eee3 f695ef3 ef2eee3 2ffc435 ef2eee3 2ffc435 ef2eee3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | """Soumission automatique au LLMDrops JDM (Phase 12).
Poste un fichier de soumission consolidé au endpoint LLMDrops de JDM,
sous un nom standardisé qui trace quel LLM l'a produit et quand. Conçu
pour être appelé depuis `write_submission_file` ou directement.
Configuration (cascade) :
- api_key : arg `api_key` → env `JDM_DROPS_API_KEY`
- endpoint_url : arg `endpoint_url` → env `JDM_DROPS_URL` → défaut
- model_name : arg `model_name` → env `LLM_MODEL` → "mcp_client"
Le fichier est posté sous `compute_submission_filename(model_name)`,
indépendant du nom local sur disque (le LLMDrops voit toujours un nom
auto-descriptif type `from_claude-sonnet-4-7_automatic_submission_14h32_27-05-26.enrich`).
"""
from __future__ import annotations
import os
from pathlib import Path
from typing import Optional
import httpx
# Charge le .env du projet pour que `JDM_DROPS_API_KEY`, `JDM_DROPS_URL` et
# `LLM_MODEL` soient disponibles même si l'appelant n'a pas importé d'autre
# composant qui charge dotenv (cas typique : `python -c "from jdm_agent.enrich
# import submit_to_jdm; ..."`).
try:
from dotenv import load_dotenv # type: ignore
load_dotenv(override=False)
except Exception:
pass
DEFAULT_ENDPOINT_URL = "http://jeuxdemots.org/LLMDrops.php"
def submit_to_jdm(
path: str | Path,
*,
api_key: Optional[str] = None,
model_name: Optional[str] = None,
endpoint_url: Optional[str] = None,
timeout: float = 30.0,
) -> dict:
"""POST le fichier de soumission au endpoint LLMDrops.
Args:
path: chemin local du fichier `.enrich` consolidé à uploader.
api_key: clé API (override env `JDM_DROPS_API_KEY` si fournie).
model_name: nom du LLM source (override env `LLM_MODEL`, fallback "mcp_client").
endpoint_url: URL du endpoint (override env `JDM_DROPS_URL`, fallback défaut).
timeout: timeout HTTP en secondes.
Returns:
{"ok": bool, "status_code": int, "response": dict | str,
"uploaded_as": str, "endpoint": str, "error": str | None}
Ne lève jamais d'exception réseau / HTTP — toute erreur est captée
et reportée dans `error` avec `ok=False`. L'appelant décide quoi
en faire.
"""
# Préparation du fichier
p = Path(path)
if not p.exists():
return {
"ok": False, "status_code": 0,
"response": None, "uploaded_as": "",
"endpoint": endpoint_url or "",
"error": f"Fichier introuvable : {p}",
}
# Résolution des paramètres (cascade)
key = api_key or os.environ.get("JDM_DROPS_API_KEY") or ""
url = endpoint_url or os.environ.get("JDM_DROPS_URL") or DEFAULT_ENDPOINT_URL
resolved_model = model_name or os.environ.get("LLM_MODEL") or "mcp_client"
# Extension dérivée du fichier source — préserve .enrich / .audit / .err
# pour que le mainteneur JDM voie immédiatement le type du drop.
# Fallback .enrich si le fichier n'a pas d'extension reconnue.
src_ext = p.suffix.lower()
if src_ext not in (".enrich", ".audit", ".err", ".stat"):
src_ext = ".enrich"
uploaded_name = compute_submission_filename(resolved_model, extension=src_ext)
if not key:
return {
"ok": False, "status_code": 0,
"response": None, "uploaded_as": uploaded_name,
"endpoint": url,
"error": (
"Clé API manquante : passe `api_key=...` ou définis "
"`JDM_DROPS_API_KEY` dans l'env. Le fichier local a été "
"écrit mais l'upload a été sauté."
),
}
headers = {"X-API-Key": key}
data = {"format": "json"}
try:
with p.open("rb") as f:
files = {"file": (uploaded_name, f, "text/plain")}
with httpx.Client(timeout=timeout) as client:
resp = client.post(url, headers=headers, data=data, files=files)
except httpx.HTTPError as e:
return {
"ok": False, "status_code": 0,
"response": None, "uploaded_as": uploaded_name,
"endpoint": url,
"error": f"Erreur réseau / HTTP : {e}",
}
except OSError as e:
return {
"ok": False, "status_code": 0,
"response": None, "uploaded_as": uploaded_name,
"endpoint": url,
"error": f"Erreur d'I/O sur le fichier : {e}",
}
# Parse la réponse — JSON si possible, brut sinon
try:
body: object = resp.json()
except Exception:
body = resp.text
ok = 200 <= resp.status_code < 300
return {
"ok": ok,
"status_code": resp.status_code,
"response": body,
"uploaded_as": uploaded_name,
"endpoint": url,
"error": None if ok else f"HTTP {resp.status_code}",
}
# Re-export pour ergonomie depuis ce module
def compute_submission_filename(model_name: str, *, now=None,
extension: str = ".enrich") -> str:
"""Délègue à `jdm_agent.enrich.pipeline.compute_submission_filename`."""
# Import local pour éviter une dépendance circulaire au load time
from jdm_agent.enrich.pipeline import compute_submission_filename as _impl
return _impl(model_name, now=now, extension=extension)
|