docs: document openai-compatible provider and FIXME_M9 corrections
Update GUIDE_DEV_PYTHON.md, TODO.md, and AGENTS.md to reflect the decisions and work done in the FEAT_M9 and FIXME_M9 sessions. GUIDE_DEV_PYTHON.md: - Header: add entry in recent updates - 3.1.2: AI_PROVIDER now documents openai-compatible with Literal type; add AI_ALLOW_INSECURE_HTTP row; move decision block after table to fix rendering - 3.2: AISettings code block updated with openai-compatible and allow_insecure_http field; decision note extended - 3.1.3: .env.example adds OpenRouter (HTTPS) and Ollama (HTTP) examples, both commented TODO.md: - M9 factory line now mentions openai-compatible with URL validation - Add FEAT_M9 and FIXME_M9 notes after M9 acceptance criteria AGENTS.md: - Section 5: new subsection for openai-compatible provider contract documenting validation rules, degraded mode, and security constraints Co-authored-by: opencode/tech-writer anthropic.claude-sonnet-4-5 <anthropic.claude-sonnet-4-5@agents.invalid>
This commit is contained in:
@@ -140,7 +140,7 @@
|
|||||||
"filename": "GUIDE_DEV_PYTHON.md",
|
"filename": "GUIDE_DEV_PYTHON.md",
|
||||||
"hashed_secret": "90bd1b48e958257948487b90bee080ba5ed00caa",
|
"hashed_secret": "90bd1b48e958257948487b90bee080ba5ed00caa",
|
||||||
"is_verified": true,
|
"is_verified": true,
|
||||||
"line_number": 4916,
|
"line_number": 4935,
|
||||||
"is_secret": false
|
"is_secret": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -177,5 +177,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"generated_at": "2026-09-07T17:24:40Z"
|
"generated_at": "2026-09-07T17:59:08Z"
|
||||||
}
|
}
|
||||||
|
|||||||
11
AGENTS.md
11
AGENTS.md
@@ -146,6 +146,17 @@ pronote-sync --dry-run
|
|||||||
- Réutiliser un téléchargement/parsing iCal pour l'agenda et les devoirs pendant un même run, sans
|
- Réutiliser un téléchargement/parsing iCal pour l'agenda et les devoirs pendant un même run, sans
|
||||||
cache global ni persistant.
|
cache global ni persistant.
|
||||||
|
|
||||||
|
### Contrat du provider `openai-compatible`
|
||||||
|
- Le provider `openai-compatible` réutilise `OpenAISynthesisProvider` avec un `base_url` personnalisé ; aucun nouveau provider n'est créé.
|
||||||
|
- `AI_BASE_URL` et `AI_MODEL` sont requis ; `AI_API_KEY` est requis (MVP).
|
||||||
|
- L'URL doit utiliser `https` sauf si `AI_ALLOW_INSECURE_HTTP=true`.
|
||||||
|
- Les credentials dans l'URL (`user:pass@host`) sont refusés.
|
||||||
|
- Les paramètres sensibles dans la *query string* sont refusés, y compris ceux sans valeur (`?token`).
|
||||||
|
- Les URL malformées ou sans hostname sont rejetées (`ValueError` catché).
|
||||||
|
- Aucune manipulation automatique de `/v1` n'est effectuée.
|
||||||
|
- Configuration incomplète ou invalide → `None` avec avertissement (mode dégradé) ; la factory ne lève jamais d'exception.
|
||||||
|
- La factory ne fait aucun appel réseau ; les avertissements utilisent `redact_url()`.
|
||||||
|
|
||||||
### Documentation (docstrings)
|
### Documentation (docstrings)
|
||||||
- **Obligatoire** : **Toute** fonction, méthode et classe publique doit avoir une docstring.
|
- **Obligatoire** : **Toute** fonction, méthode et classe publique doit avoir une docstring.
|
||||||
- **Format** : Utiliser le format **Sphinx/reST** (pas Google ou NumPy) pour une compatibilité native avec Sphinx.
|
- **Format** : Utiliser le format **Sphinx/reST** (pas Google ou NumPy) pour une compatibilité native avec Sphinx.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
> **⚠️ À noter** : Ce guide est **volontairement détaillé** pour préserver les connaissances acquises sur les spécificités des flux Pronote (iCal) et les décisions architecturales du projet TypeScript. Certaines sections (ex: parsing iCal) contiennent des **observations précises** issues de l'analyse du code existant.
|
> **⚠️ À noter** : Ce guide est **volontairement détaillé** pour préserver les connaissances acquises sur les spécificités des flux Pronote (iCal) et les décisions architecturales du projet TypeScript. Certaines sections (ex: parsing iCal) contiennent des **observations précises** issues de l'analyse du code existant.
|
||||||
> **Mises à jour récentes** :
|
> **Mises à jour récentes** :
|
||||||
|
> - Ajout du provider ``openai-compatible`` dans la section **[3. Configuration](#3-configuration-denvironnement)** (variables §3.1.2, modèle §3.2, exemple §3.1.3) et la factory **[§9.5](#95-factory-pour-les-fournisseurs-ia-synthesis__init__py)** pour supporter les endpoints compatibles OpenAI (OpenRouter, Ollama, proxy LiteLLM) avec validation stricte de l'URL et opt-in HTTP.
|
||||||
> - Ajout de la section **[5 bis. Sources externes : blog du collège (RSS)](#5-bis-sources-externes--blog-du-collège-rss)** pour le parsing du flux RSS du blog.
|
> - Ajout de la section **[5 bis. Sources externes : blog du collège (RSS)](#5-bis-sources-externes--blog-du-collège-rss)** pour le parsing du flux RSS du blog.
|
||||||
> - Mise à jour de la section **[10. Envoi XMPP](#10-envoi-xmpp)** avec la décision architecturale (compte bot dédié, messages directs, pas de PubSub).
|
> - Mise à jour de la section **[10. Envoi XMPP](#10-envoi-xmpp)** avec la décision architecturale (compte bot dédié, messages directs, pas de PubSub).
|
||||||
> - Intégration des modèles `BlogArticle` et `ExternalInfo` dans la section **[6. Modèle de données Pydantic](#6-modèle-de-données-pydantic)**.
|
> - Intégration des modèles `BlogArticle` et `ExternalInfo` dans la section **[6. Modèle de données Pydantic](#6-modèle-de-données-pydantic)**.
|
||||||
@@ -299,22 +300,23 @@ d'un besoin réel et testé.
|
|||||||
| `PRONOTE_MESSAGES_SOURCE` | Source pour les messages (`pronotepy` uniquement). | `pronotepy` | `Literal` |
|
| `PRONOTE_MESSAGES_SOURCE` | Source pour les messages (`pronotepy` uniquement). | `pronotepy` | `Literal` |
|
||||||
| `SYNC_PAST_DAYS` | Nombre de jours dans le passé pour la sync CalDAV. | `7` | `int` |
|
| `SYNC_PAST_DAYS` | Nombre de jours dans le passé pour la sync CalDAV. | `7` | `int` |
|
||||||
| `SYNC_FUTURE_DAYS` | Nombre de jours dans le futur pour la sync CalDAV. | `30` | `int` |
|
| `SYNC_FUTURE_DAYS` | Nombre de jours dans le futur pour la sync CalDAV. | `30` | `int` |
|
||||||
|
|
||||||
> ⚠️ **Décision d'implémentation** :
|
|
||||||
> Ces variables sont désormais dans `AppSettings` (et non `CalDAVSettings`) car `CalDAVSettings` utilise `env_prefix="CALDAV_"`, ce qui nécessiterait `CALDAV_SYNC_PAST_DAYS`.
|
|
||||||
> Leur placement dans `AppSettings` (sans préfixe) garantit un mappage correct avec `SYNC_PAST_DAYS` / `SYNC_FUTURE_DAYS`.
|
|
||||||
| `THEORETICAL_AGENDA_PATH` | Chemin vers le fichier JSON de l'agenda théorique. | `None` | `str \| None`|
|
| `THEORETICAL_AGENDA_PATH` | Chemin vers le fichier JSON de l'agenda théorique. | `None` | `str \| None`|
|
||||||
| `SCHOOL_HOLIDAYS_PATH` | Chemin vers le fichier JSON des vacances scolaires. | `None` | `str \| None`|
|
| `SCHOOL_HOLIDAYS_PATH` | Chemin vers le fichier JSON des vacances scolaires. | `None` | `str \| None`|
|
||||||
| `THEORETICAL_WEEK_ANCHOR_DATE` | Date de référence pour la parité des semaines (paire/impaire). | `None` | `date \| None`|
|
| `THEORETICAL_WEEK_ANCHOR_DATE` | Date de référence pour la parité des semaines (paire/impaire). | `None` | `date \| None`|
|
||||||
| `THEORETICAL_WEEK_ANCHOR_TYPE` | Parité de la semaine de référence (`even` ou `odd`). | `None` | `Literal["even", "odd"] \| None`|
|
| `THEORETICAL_WEEK_ANCHOR_TYPE` | Parité de la semaine de référence (`even` ou `odd`). | `None` | `Literal["even", "odd"] \| None`|
|
||||||
| `AI_ENABLED` | Activer la synthèse IA. | `False` | `bool` |
|
| `AI_ENABLED` | Activer la synthèse IA. | `False` | `bool` |
|
||||||
| `AI_PROVIDER` | Fournisseur IA (`openai` ou `litellm`). | `openai` | `str` |
|
| `AI_PROVIDER` | Fournisseur IA (`openai`, `openai-compatible` ou `litellm`). | `openai` | `Literal["openai", "litellm", "openai-compatible"]` |
|
||||||
| `AI_BASE_URL` | URL de base pour l'API IA (ex: OpenAI compatible). | `None` | `str \| None`|
|
| `AI_BASE_URL` | URL de base pour l'API IA (ex: OpenAI compatible). | `None` | `str \| None`|
|
||||||
| `AI_API_KEY` | Clé API pour l'API IA. | `None` | `SecretStr` |
|
| `AI_API_KEY` | Clé API pour l'API IA. | `None` | `SecretStr` |
|
||||||
| `AI_MODEL` | Modèle IA à utiliser (exemple recommandé : `gpt-4o-mini`). | `None` | `str \| None`|
|
| `AI_MODEL` | Modèle IA à utiliser (exemple recommandé : `gpt-4o-mini`). | `None` | `str \| None`|
|
||||||
|
| `AI_ALLOW_INSECURE_HTTP` | Autoriser HTTP (non sécurisé) pour `openai-compatible` uniquement. | `False` | `bool` |
|
||||||
| `DRY_RUN` | Mode dry-run (pas de modifications CalDAV/XMPP). | `False` | `bool` |
|
| `DRY_RUN` | Mode dry-run (pas de modifications CalDAV/XMPP). | `False` | `bool` |
|
||||||
| `LOG_LEVEL` | Niveau de log (`DEBUG`, `INFO`, `WARNING`, `ERROR`). | `INFO` | `str` |
|
| `LOG_LEVEL` | Niveau de log (`DEBUG`, `INFO`, `WARNING`, `ERROR`). | `INFO` | `str` |
|
||||||
|
|
||||||
|
> ⚠️ **Décision d'implémentation** :
|
||||||
|
> Ces variables sont désormais dans `AppSettings` (et non `CalDAVSettings`) car `CalDAVSettings` utilise `env_prefix="CALDAV_"`, ce qui nécessiterait `CALDAV_SYNC_PAST_DAYS`.
|
||||||
|
> Leur placement dans `AppSettings` (sans préfixe) garantit un mappage correct avec `SYNC_PAST_DAYS` / `SYNC_FUTURE_DAYS`.
|
||||||
|
|
||||||
|
|
||||||
#### 3.1.3 Exemple de fichier `.env.example`
|
#### 3.1.3 Exemple de fichier `.env.example`
|
||||||
|
|
||||||
@@ -360,6 +362,20 @@ AI_BASE_URL=https://api.openai.com/v1
|
|||||||
AI_API_KEY=your_ai_api_key
|
AI_API_KEY=your_ai_api_key
|
||||||
# AI_MODEL=gpt-4o-mini # exemple recommandé, non activé par défaut
|
# AI_MODEL=gpt-4o-mini # exemple recommandé, non activé par défaut
|
||||||
|
|
||||||
|
# Exemple : OpenRouter (HTTPS, provider openai-compatible)
|
||||||
|
# AI_PROVIDER=openai-compatible
|
||||||
|
# AI_BASE_URL=https://openrouter.ai/api/v1
|
||||||
|
# AI_MODEL=fournisseur/modele
|
||||||
|
# AI_API_KEY=your-openrouter-key
|
||||||
|
# AI_ALLOW_INSECURE_HTTP=false
|
||||||
|
|
||||||
|
# Exemple : Ollama local (HTTP, provider openai-compatible)
|
||||||
|
# AI_PROVIDER=openai-compatible
|
||||||
|
# AI_BASE_URL=http://127.0.0.1:11434/v1
|
||||||
|
# AI_MODEL=modele-local
|
||||||
|
# AI_API_KEY=local-not-required
|
||||||
|
# AI_ALLOW_INSECURE_HTTP=true
|
||||||
|
|
||||||
# --- Divers ---
|
# --- Divers ---
|
||||||
DRY_RUN=false
|
DRY_RUN=false
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
@@ -376,6 +392,8 @@ LOG_LEVEL=INFO
|
|||||||
> `sync_past_days` et `sync_future_days` sont dans `AppSettings`, et non `CalDAVSettings`.
|
> `sync_past_days` et `sync_future_days` sont dans `AppSettings`, et non `CalDAVSettings`.
|
||||||
> `CalDAVSettings.calendar_path` a pour valeur par défaut `"/pronote-sync/"`.
|
> `CalDAVSettings.calendar_path` a pour valeur par défaut `"/pronote-sync/"`.
|
||||||
> `XmppSettings.resource` a pour valeur par défaut `"pronote-sync"`.
|
> `XmppSettings.resource` a pour valeur par défaut `"pronote-sync"`.
|
||||||
|
> > ``AISettings.provider`` accepte également ``openai-compatible`` (réutilise ``OpenAISynthesisProvider`` avec un ``base_url`` personnalisé).
|
||||||
|
> > ``AISettings.allow_insecure_http`` (défaut ``False``) autorise les URLs HTTP pour le provider ``openai-compatible`` uniquement.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
@@ -409,9 +427,10 @@ class CalDAVSettings(BaseSettings):
|
|||||||
class AISettings(BaseSettings):
|
class AISettings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(env_prefix="AI_", env_file=".env", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="AI_", env_file=".env", extra="ignore")
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
provider: Literal["openai", "litellm"] = "openai"
|
provider: Literal["openai", "litellm", "openai-compatible"] = "openai"
|
||||||
base_url: str | None = None
|
base_url: str | None = None
|
||||||
api_key: SecretStr | None = None
|
api_key: SecretStr | None = None
|
||||||
|
allow_insecure_http: bool = False
|
||||||
model: str | None = None
|
model: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
13
TODO.md
13
TODO.md
@@ -176,7 +176,7 @@ Générer une synthèse optionnelle via un fournisseur IA, avec mode dégradé s
|
|||||||
- [x] Créer `synthesis/provider.py` : protocole `SynthesisProvider.generate → Optional[SynthesisResult]` (ne lève jamais d'exception).
|
- [x] Créer `synthesis/provider.py` : protocole `SynthesisProvider.generate → Optional[SynthesisResult]` (ne lève jamais d'exception).
|
||||||
- [x] Créer `synthesis/openai.py` : `OpenAISynthesisProvider` (httpx, prompt système FR, max 800 car., timeout 30 s, temp 0.3).
|
- [x] Créer `synthesis/openai.py` : `OpenAISynthesisProvider` (httpx, prompt système FR, max 800 car., timeout 30 s, temp 0.3).
|
||||||
- [x] Créer `synthesis/litellm.py` : `LiteLLMSynthesisProvider` (optionnel, extra `ai-litellm`).
|
- [x] Créer `synthesis/litellm.py` : `LiteLLMSynthesisProvider` (optionnel, extra `ai-litellm`).
|
||||||
- [x] Créer `synthesis/__init__.py` : factory `get_synthesis_provider(settings)` (OpenAI par défaut, litellm si `AI_PROVIDER=litellm`).
|
- [x] Créer `synthesis/__init__.py` : factory `get_synthesis_provider(settings)` (OpenAI par défaut, litellm si `AI_PROVIDER=litellm`, `openai-compatible` si `AI_PROVIDER=openai-compatible` avec validation d'URL).
|
||||||
- [x] Mode dégradé : clé absente / timeout / exception → retour `None` (le pipeline continue sans synthèse).
|
- [x] Mode dégradé : clé absente / timeout / exception → retour `None` (le pipeline continue sans synthèse).
|
||||||
- [x] Respecter les contraintes (3-5 phrases, ton sobre, pas d'emoji dans le texte IA).
|
- [x] Respecter les contraintes (3-5 phrases, ton sobre, pas d'emoji dans le texte IA).
|
||||||
|
|
||||||
@@ -185,6 +185,17 @@ Générer une synthèse optionnelle via un fournisseur IA, avec mode dégradé s
|
|||||||
- Clé absente ou erreur réseau → `None` (aucune exception propagée).
|
- Clé absente ou erreur réseau → `None` (aucune exception propagée).
|
||||||
- La factory renvoie le bon provider ; litellm derrière l'extra optionnel.
|
- La factory renvoie le bon provider ; litellm derrière l'extra optionnel.
|
||||||
|
|
||||||
|
> **Évolution FEAT_M9 — Provider `openai-compatible`** :
|
||||||
|
> Le provider `openai-compatible` a été ajouté à `get_synthesis_provider` (commit `13e058f` sur `feat/m9-custom-endpoint`).
|
||||||
|
> Il réutilise `OpenAISynthesisProvider` avec un `base_url` validé (HTTPS obligatoire, HTTP via `AI_ALLOW_INSECURE_HTTP=true`).
|
||||||
|
> Configuration incomplète → `None` + warning (mode dégradé). Aucun appel réseau à la factory.
|
||||||
|
> Couverture synthesis : 91,57 % (13 tests factory ajoutés).
|
||||||
|
>
|
||||||
|
> **Corrections FIXME_M9 — Audit synthèse IA** :
|
||||||
|
> Cinq points d'audit corrigés (commit `19cbf8f` sur `fix/m9-fixme`, mergé en `2a27225`) :
|
||||||
|
> `redact_secrets(extra_secrets=...)`, `SecretStr` préservé dans les providers, contenu du message dans `_build_prompt`,
|
||||||
|
> `_validate_output` (rejet emoji/titre/liste/HTML), `importorskip` pour les tests litellm.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## M10. Canal XMPP — Priorité : Haute
|
## M10. Canal XMPP — Priorité : Haute
|
||||||
|
|||||||
Reference in New Issue
Block a user