Skip to content

test(ia): consolidation Ollama + workers + prompt injection + CI intégration (+89 tests) - #13

Merged
jeremie0342 merged 8 commits into
masterfrom
feat/tests-ia-consolidation-ollama
Jul 22, 2026
Merged

test(ia): consolidation Ollama + workers + prompt injection + CI intégration (+89 tests)#13
jeremie0342 merged 8 commits into
masterfrom
feat/tests-ia-consolidation-ollama

Conversation

@jeremie0342

Copy link
Copy Markdown
Collaborator

Summary

Consolidation de la couverture de tests de la partie IA (skilluv-ia) avant le renforcement pré-prod. Trois sessions de travail regroupées ici.

+89 tests unitaires (293 → 382), +9 tests d'intégration Ollama, nouveau job CI Redis/MinIO.

1. feat(llm) — Consolidation Ollama (6a049a3)

Décision stratégique : tout passe par Ollama, plus par Claude. Perf court terme sacrifiée pour la souveraineté données et l'IA custom long terme.

  • Provider hardening : max_retries configurable, retry prompt inclut la réponse invalide précédente, détection 404 modèle non pull avec message actionnable, détection content vide/whitespace.
  • Config unifiée : les 3 tiers pointent tous sur qwen2.5-coder:7b. Le 3B collapse de façon reproductible sur les schémas nested (validé empiriquement contre OrientationSuggestion).
  • talent_analyzer._CAREER_SCHEMA : alignement required avec les defaults Pydantic + prompt système durci (enums en anglais, interdiction d'extra fields).
  • code_reviewer._fence_safe() : neutralisation triple-backticks (zero-width space) + NULL bytes dans les inputs utilisateur avant injection dans les code fences markdown.

2. test — Nouveaux tests (828bc4d)

  • test_workers_envelope.py (33 tests) — 4 scénarios × 8 workers arq (plagiarism, code_review, talent_match, recommendation, media replay/clip, analytics hidden_gems/churn). Pattern paramétrisé via WorkerCase. Fakeredis + services mockés. ~6s.
  • test_code_reviewer_service.py (31 tests) — service review_code testé en unité (jusqu'ici uniquement via gRPC servicer).
  • test_prompt_injection.py (19 tests) — surfaces d'injection identifiées, défense _fence_safe validée, bornes Pydantic vérifiées.
  • test_llm_provider.py — +6 tests d'erreurs LLM approfondies (ReadTimeout, 500, 404, content vide, metric).
  • tests/integration/test_ollama_live.py (5 tests) — provider testé contre vrai Ollama local (marker local_llm, auto-skip).
  • tests/integration/test_services_llm_live.py (4 tests) — services LLM end-to-end contre Ollama réel. suggest_career_path marqué @pytest.mark.flaky(reruns=2) — flakiness assumée du LLM local out-of-the-box.

Dépendance ajoutée : pytest-rerunfailures>=15.0. Markers custom local_llm + integration.

3. ci — Job intégration Redis/MinIO (9a2ac2d)

Nouveau job integration-tests qui monte Redis 7 alpine + Bitnami MinIO en services GitHub Actions (health checks). Exécute tests/integration/ — sauf test_grpc_full_chain.py (HF Hub ~1 GB) et les tests Ollama live (pas de LLM disponible en CI).

Test plan

  • Job check (unit tests, ruff) reste vert — 382 tests
  • Nouveau job integration-tests doit passer avec Redis + MinIO en services
  • Aucune régression sur les tests existants
  • Vérifier que le healthcheck MinIO Bitnami répond bien (première utilisation de cette image en CI)
  • Vérifier localement : RUN_LOCAL_LLM=1 uv run pytest -m local_llm passe si Ollama + Qwen 7B + 3B pull

Notes

  • Aucun trou critique restant sur la carto des tests IA après ce PR.
  • Le test flaky (career_path) est documenté comme "best-effort tant que le modèle n'est pas fine-tuné sur les données Skilluv".
  • Les 3 commits sont thématiques et peuvent être review séparément.

…tions

Vague 1.2 de l'audit d'upgrade (safe wins IA).

## Bumps toolchain
- uv 0.5.11 → 0.11.30 (Dockerfile builder + CI workflow) — 12 mois de retard,
  gain de perf resolver + fixes.
- astral-sh/setup-uv v3 → v8 (dernière stable) avec version pinnée.

## Pins Docker (fin des :latest flottants)
- redis:7-alpine → redis:8.8-alpine (docker-compose × 2)
- minio/minio:latest → RELEASE.2025-10-15T17-29-55Z (× 2)
- prom/prometheus:latest → v3.13.1 (× 2)
- prom/alertmanager:latest → v0.33.1 (prod only)
- grafana/grafana:latest → 13.0.4 (× 2)

## Supply-chain
- jlumbroso/free-disk-space@main → pin SHA (54081f1, 2023-10-18).

## Notes
- ollama/ollama:latest laissé (dev-only, projet qui bouge vite).
- python:3.12-slim inchangé (attendre 3.13 alignement torch/sentence-transformers).
Décision stratégique : full Ollama pour tous les tiers (souveraineté données,
IA custom long terme). Perf court terme sacrifiée assumée.

Provider Ollama :
- max_retries configurable via `settings.ollama_max_retries` (défaut 2).
- Retry prompt inclut la réponse précédente — évite au petit modèle de
  repartir de zéro et perdre la structure globale entre attempts.
- Détection modèle non pull (HTTP 404) → ExternalServiceError avec message
  actionnable `run \`ollama pull <model>\`` au lieu d'une erreur générique.
- Détection content vide / whitespace-only (200 OK mais réponse creuse,
  symptôme OOM ou modèle non chargé) → ExternalServiceError explicite au
  lieu de tomber sur JSONDecodeError et gaspiller les retries.

Config :
- Les 3 tiers PREMIUM / STANDARD / FAST pointent tous sur qwen2.5-coder:7b.
  Le 3B collapse de façon reproductible sur les schémas nested — testé
  contre le schéma `OrientationSuggestion` de talent_analyzer.
- .env.example aligné avec commentaire explicatif.

Services :
- talent_analyzer._CAREER_SCHEMA : `required` réduit aux champs sans default
  Pydantic (orientation_slug, confidence). Les autres (match_reason,
  required_skills_missing, transition_effort, timeline_estimate_months)
  ont des defaults dans OrientationSuggestion — les marquer required forçait
  les LLM à halluciner. Prompt système durci sur enums en anglais et
  interdiction des champs hors-schéma.
- code_reviewer._fence_safe() : neutralise les triple-backticks (zero-width
  space) + NULL bytes dans source_code / test_output avant injection dans
  les code fences markdown. Défense contre le prompt injection classique
  \`\`\` IGNORE ALL INSTRUCTIONS \`\`\`.
+89 tests unitaires (293 → 382) + 9 tests d'intégration Ollama.

Nouveaux tests unitaires :
- tests/test_workers_envelope.py (33 tests) : 4 scénarios × 8 workers arq
  couverts (plagiarism, code_review, talent_match, recommendation, media
  replay/clip, analytics hidden_gems/churn). Happy path + idempotence +
  SkilluvAIError + Exception générique. Infra fakeredis + services mockés.
  Rapide (~6s), déterministe. Pattern paramétrisé via WorkerCase — ajouter
  un futur worker = ajouter une entrée dans la liste WORKERS.
- tests/test_code_reviewer_service.py (31 tests) : service review_code testé
  en unité (jusqu'ici uniquement via gRPC servicer). Court-circuit
  soumission vide, calcul fragments_bonus par paliers, ton system prompt
  selon user_level, troncatures test_output/source_code, robustesse aux
  réponses LLM partielles, propagation d'identifiants, tier PREMIUM.
- tests/test_prompt_injection.py (19 tests) : surfaces d'injection
  identifiées (source_code, test_output, tags, orientation_slug,
  programming_language, project_id), défense _fence_safe validée, bornes
  Pydantic vérifiées. Tests structurels — le comportement adversarial du
  LLM lui-même n'est pas testé (non-déterministe).
- tests/test_llm_provider.py : +6 tests d'erreurs LLM approfondies
  (ReadTimeout, 500 server error, 404 model not pulled, empty content,
  whitespace-only content, external_errors_total incrémenté).

Nouveaux tests d'intégration :
- tests/integration/test_ollama_live.py (5 tests) : provider testé contre
  vrai Ollama local, pin sur Qwen 3B. Auto-skip si Ollama down. Marker
  `local_llm`. Health, structured outputs sur schémas réels code_review +
  career_path, chemins d'erreur (modèle inexistant, schéma impossible).
- tests/integration/test_services_llm_live.py (4 tests) : services LLM
  testés end-to-end contre vrai Ollama, defaults prod (7B). Le test
  suggest_career_path est marqué @pytest.mark.flaky(reruns=2) — le LLM
  local out-of-the-box est probabiliste sur schémas complexes (~50-70%
  succès en 1 essai). À supprimer une fois le modèle fine-tuné.

Infra :
- pyproject.toml : ajout dep pytest-rerunfailures + markers custom
  (local_llm, integration).
- uv.lock : rafraîchi.
Les tests tests/integration/{redis,minio,redis_services}_integration.py
existent depuis longtemps mais n'étaient jamais joués en CI faute de
services disponibles. Ils dormaient localement.

Job `integration-tests` :
- Ne tourne qu'après le job `check` (unit) — pas de gaspillage si le
  fondement casse.
- Redis 7 alpine + Bitnami MinIO en services Docker Compose GitHub Actions
  avec health checks (attente que les services soient prêts).
- Env vars REDIS_URL / MINIO_ENDPOINT pointent vers localhost:{6379,9000}.
- Bitnami MinIO démarre en mode server par défaut, MINIO_DEFAULT_BUCKETS
  pré-crée le bucket skilluv-media.

Exclusions explicites :
- test_grpc_full_chain.py : pull sentence-transformers (~1 GB HF Hub) et
  pré-warme les embeddings de plagiat. Trop lourd/lent pour la CI. À lancer
  en local uniquement.
- test_ollama_live.py + test_services_llm_live.py : nécessitent un serveur
  Ollama local — pas viable en CI GitHub Actions (aucun modèle pré-pull).
… manuel

`bitnami/minio:latest` renvoie `manifest unknown` sur Docker Hub — Bitnami
a supprimé cette image. Retour à l'image officielle `minio/minio`, mais
elle exige l'argument `server /data` que GitHub Actions ne permet pas de
passer via `services:` (pas de `command:`).

Solution : démarrer MinIO en step `docker run` avec health check manuel.
Plus verbose mais fiable et sans dépendance à un image tierce.
Le modèle `TestCase` a évolué pour rendre `description` obligatoire (voir
src/models/challenge.py). Le fixture `_sample_challenge` passait un
`name="basic"` qui était silencieusement ignoré (Pydantic accepte les
extras par défaut) et omettait `description`.

Ces tests n'ont jamais tourné en CI jusqu'ici (job intégration nouveau) —
personne n'avait vu la dérive. Corrigé.
@jeremie0342
jeremie0342 merged commit 3db28a9 into master Jul 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant