| """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 |
|
|
| |
| |
| |
| |
| try: |
| from dotenv import load_dotenv |
| 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. |
| """ |
| |
| 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}", |
| } |
|
|
| |
| 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" |
| |
| |
| |
| 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}", |
| } |
|
|
| |
| 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}", |
| } |
|
|
|
|
| |
| def compute_submission_filename(model_name: str, *, now=None, |
| extension: str = ".enrich") -> str: |
| """Délègue à `jdm_agent.enrich.pipeline.compute_submission_filename`.""" |
| |
| from jdm_agent.enrich.pipeline import compute_submission_filename as _impl |
| return _impl(model_name, now=now, extension=extension) |
|
|