Lancer une étape individuellement
Le pipeline complet se lance d’un seul argo submit (voir l’accueil). Mais pour déboguer, relancer une étape échouée ou itérer sur une seule brique, on peut exécuter chaque étape isolément — soit via Argo, soit en local. Cette page rassemble les commandes vérifiées pour les deux modes.
run_id et run_date
Par défaut, run_id vaut le nom du workflow Argo et run_date sa date de création. Une étape relancée isolément crée donc un nouveau dossier S3 vide si on ne lui passe pas explicitement le run_id/run_date du run d’origine — et elle ne trouvera pas ses entrées. Récupérez-les depuis le nom du workflow d’origine (argo list) ou depuis le chemin S3 …/workflow_runs/{run_date}/{run_id}/.
Prérequis : secrets et variables d’environnement
Via Argo, les secrets sont injectés depuis secret-codif-coicop-bdf (cf. README.md). En local, il faut exporter soi-même les variables requises par l’étape :
| Étape | Variables d’environnement requises |
|---|---|
build-datasets, classify-regex, classify-lcs, export-results |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_S3_ENDPOINT, AWS_ENDPOINT_URL |
prune-codes |
AWS_* + DDC_ENCRYPTION_KEY |
index-notices, index-annotations (workflows d’indexation) |
AWS_* + DDC_ENCRYPTION_KEY + LLMLAB_URL, LLMLAB_API_KEY + QDRANT_URL, QDRANT_API_KEY, QDRANT_API_PORT |
classify-rag-notices, classify-rag-annotations |
AWS_* + DDC_ENCRYPTION_KEY + LLMLAB_* + QDRANT_* + MLFLOW_TRACKING_URI, MLFLOW_TRACKING_USERNAME, MLFLOW_TRACKING_PASSWORD + LANGFUSE_BASE_URL, LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY |
classify-ttc |
AWS_* + MLFLOW_TRACKING_* (chargement du modèle depuis MLflow) |
reconcile-llm |
AWS_* + LLMLAB_API_KEY (+ LLMLAB_URL pour un endpoint non-OpenAI) |
report |
AWS_* + MLFLOW_TRACKING_* (facultatif : sans MLFLOW_TRACKING_URI, le log MLflow est sauté) |
evaluate |
idem report |
reconcile-sirus |
AWS_* + MLFLOW_TRACKING_* (résolution de reconcile-sirus-model-uri) |
entraînement SIRUS (reconcile-sirus/train.sh, hors pipeline) |
AWS_* + MLFLOW_TRACKING_* — obligatoires ici, contrairement au report : le modèle et ses métriques n’ont pas d’autre destination, et le script refuse de démarrer sans MLFLOW_TRACKING_URI |
Le dépôt contient trois workflows. index-notices et index-annotations ne sont plus des templates de argo/codif-pipeline.yaml : elles vivent dans argo/index-notices-pipeline.yaml et argo/index-annotations-pipeline.yaml, qui sont eux-mêmes des chaînes courtes et se relancent en entier (voir Reconstruire une collection Qdrant).
Mode 1 — via Argo (--entrypoint)
Chaque étape est un template nommé dans argo/codif-pipeline.yaml. L’option --entrypoint lance ce template seul ; ses paramètres d’entrée sont résolus par nom depuis les -p passés en ligne de commande.
# Étapes simples : seuls run_id / run_date sont requis
argo submit argo/codif-pipeline.yaml --entrypoint classify-regex \
-p run_id=codif-abc12 -p run_date=2026-03-20
argo submit argo/codif-pipeline.yaml --entrypoint classify-lcs \
-p run_id=codif-abc12 -p run_date=2026-03-20
argo submit argo/codif-pipeline.yaml --entrypoint prune-codes \
-p run_id=codif-abc12 -p run_date=2026-03-20
# build-datasets : il faut aussi le fichier d'entrée et le mappage de colonnes
argo submit argo/codif-pipeline.yaml --entrypoint build-datasets \
-p run_id=codif-abc12 -p run_date=2026-03-20 \
-p input_file=s3://projet-budget-famille/data/workflow_inputs/mon_fichier.csv \
-p text_column=NAT_DEP -p shop_column=MAG_DEP -p budget_column=MONT_DEP \
-p annee_column= -p source_column=
# classify-rag-notices / classify-rag-annotations : le nom de la collection Qdrant est
# OBLIGATOIRE. Attention, l'input du template s'appelle `collection-name` (et non
# classify-rag-*-collection, qui est le nom du paramètre global du workflow).
# Pas de sampling ici : il est centralisé à classify-regex.
argo submit argo/codif-pipeline.yaml --entrypoint classify-rag-notices \
-p run_id=codif-abc12 -p run_date=2026-03-20 -p classify-rag-model=gemma4-26b-moe \
-p collection-name=coicop_notices__2026-09-02__index-notices-a7k2p
argo submit argo/codif-pipeline.yaml --entrypoint classify-rag-annotations \
-p run_id=codif-abc12 -p run_date=2026-03-20 -p classify-rag-model=gemma4-26b-moe \
-p collection-name=coicop_annotations__2026-09-02__index-annotations-b3x9q
# classify-ttc : classify-ttc-model-uri a une valeur par défaut, surchargeable
argo submit argo/codif-pipeline.yaml --entrypoint classify-ttc \
-p run_id=codif-abc12 -p run_date=2026-03-20
# export-results : reprend le mappage de colonnes pour restaurer les noms d'origine
argo submit argo/codif-pipeline.yaml --entrypoint export-results \
-p run_id=codif-abc12 -p run_date=2026-03-20 \
-p text_column=NAT_DEP -p shop_column=MAG_DEP -p budget_column=MONT_DEP -p annee_column=En --entrypoint, Argo résout les inputs d’un template par nom depuis les paramètres du workflow, puis depuis les -p de la ligne de commande. Un input dont le nom n’existe pas parmi les paramètres globaux doit donc être fourni explicitement — c’est le cas de collection-name (les deux étapes RAG) et de step-timings (le rapport) :
# reconcile-llm : les inputs portent bien le nom des paramètres globaux
argo submit argo/codif-pipeline.yaml --entrypoint reconcile-llm \
-p run_id=codif-abc12 -p run_date=2026-03-20 \
-p reconcile-llm-model=gemma4-26b-moe -p reconcile-llm-concurrency=5
# report : "step-timings" n'est pas un paramètre du workflow, il faut le passer
argo submit argo/codif-pipeline.yaml --entrypoint report \
-p run_id=codif-abc12 -p run_date=2026-03-20 \
-p input_file=s3://.../workflow_inputs/mon_fichier.csv -p report-experiment=codif-coicop-eval \
-p classify-rag-model= -p reconcile-llm-model=gemma4-26b-moe \
-p reconcile-llm-concurrency=5 -p classify-ttc-model-uri= \
-p classify-rag-notices-collection= -p classify-rag-annotations-collection= \
-p skip-report=false -p step-timings='{}'En cas de doute, valider avec argo submit --dry-run … avant de soumettre.
Plutôt que de relancer étape par étape, argo retry <nom-du-workflow> reprend le DAG d’origine là où il a échoué, avec les mêmes paramètres (donc le même run_id).
Mode 2 — en local
Les commandes ci-dessous sont celles exécutées par les conteneurs Argo, à lancer depuis la racine du dépôt après avoir exporté les variables d’environnement du tableau ci-dessus.
Le dépôt est un workspace uv : un pyproject.toml par module, mais un seul uv.lock à la racine et un seul environnement (.venv à la racine). Conséquence pratique :
uv syncdepuis un dossier de module n’installe que les dépendances de ce module, aux versions fixées par le lock de la racine — donc les mêmes versions que dans le pipeline ;- l’environnement est remplacé à chaque
uv sync: enchaîner deux modules réinstalle ce qu’il faut, ce qui est normal et rapide (le cacheuvest local) ; - pour ajouter une dépendance à un module, depuis n’importe où :
uv add --package <module> <paquet>; --lockedfait échouer la commande si le lock ne correspond plus auxpyproject.toml(au lieu de re-résoudre en silence). C’est ce que fait le pipeline ; en local, l’omettre est sans danger.
RUN_ID=codif-abc12
RUN_DATE=2026-03-20
RUN_ROOT="s3://projet-budget-famille/data/workflow_runs/$RUN_DATE/$RUN_ID"1. build-datasets
cd build-datasets && uv sync --locked
uv run main.py --run-id $RUN_ID --run-date $RUN_DATE \
--input-file s3://projet-budget-famille/data/workflow_inputs/mon_fichier.csv \
--text-column NAT_DEP --shop-column MAG_DEP --budget-column MONT_DEP--input-file est obligatoire. Ajouter --label-column code si le fichier porte une vérité terrain : elle est recopiée dans code et rend le run évaluable.
2. classify-regex
cd classify-regex && uv sync --locked
uv run src/main.py --run-id $RUN_ID --run-date $RUN_DATE3. classify-lcs (R)
cd classify-lcs
Rscript R/main.R --run-id=$RUN_ID --run-date=$RUN_DATELe parsing des options de R/main.R impose la syntaxe --option=valeur (signe =, pas d’espace).
4. prune-codes (depuis prune-codes/)
cd prune-codes && uv sync --locked
uv run scripts/main.py --config config/config.yaml \
--run-id $RUN_ID --run-date $RUN_DATEProduit les 5 artefacts prunés sous $RUN_ROOT/prune-codes/ (voir 3. Prune). À lancer avant les étapes RAG. Le drapeau --only {all,nomenclature,kb} restreint le périmètre ; all (défaut) est ce qu’exécute le pipeline de codification.
5. classify-rag-notices (depuis rag-notices/)
cd rag-notices && uv sync --locked
uv run scripts/2_run_rag.py --config config/config.yaml \
--run-id $RUN_ID --run-date $RUN_DATE \
--collection_name coicop_notices__2026-09-02__index-notices-a7k2p \
--model_name gemma4-26b-moe # facultatif--collection_name est obligatoire : il n’y a plus de nom de collection par défaut dans la config, précisément pour qu’un oubli échoue au lieu de retomber en silence sur l’index d’un autre run. Le script valide la collection contre son manifeste avant de commencer.
Attention au mélange de styles : le script attend --run-id/--run-date (tirets) mais --collection_name/--model_name/--sample_size (tirets bas). Le script d’indexation 0_create_vector_db.py, plus récent, utilise --collection-name (tirets).
6. classify-rag-annotations (depuis rag-annotations/)
cd rag-annotations && uv sync --locked
uv run scripts/1_run_rag.py --config config/config.yaml \
--run-id $RUN_ID --run-date $RUN_DATE \
--collection-name coicop_annotations__2026-09-02__index-annotations-b3x9q--collection-name est obligatoire, pour la même raison qu’au-dessus.
Sortie : $RUN_ROOT/classify-rag-annotations/predictions.parquet.
6bis. Reconstruire une collection Qdrant
L’indexation ne fait plus partie du pipeline de codification : elle a ses deux workflows, à relancer en entier (ce sont des chaînes de deux ou trois étapes).
# Notices : autonome, la nomenclature vient d'un CSV statique
argo submit argo/index-notices-pipeline.yaml
# Annotations : la KB, c'est toute la base annotée ; kb-sample-size pour un index d'essai
argo submit argo/index-annotations-pipeline.yaml
argo submit argo/index-annotations-pipeline.yaml -p kb-sample-size=1000Chacun imprime en fin d’exécution la ligne à recopier dans argo/params.yaml (classify-rag-notices-collection: / classify-rag-annotations-collection:).
En local, les deux constructeurs se lancent comme les autres scripts — le RUN_ID/RUN_DATE est alors celui du run d’indexation, pas celui d’un run de codification, et c’est lui qui se retrouve dans le nom de la collection :
# après `prune-codes --only nomenclature` sur le même RUN_ID/RUN_DATE
cd rag-notices && uv sync --locked
uv run scripts/0_create_vector_db.py --config config/config.yaml \
--run-id $RUN_ID --run-date $RUN_DATE
# après `prune-codes --only kb` sur le même RUN_ID/RUN_DATE
cd rag-annotations && uv sync --locked
uv run scripts/0_build_annotation_vector_db.py --config config/config.yaml \
--run-id $RUN_ID --run-date $RUN_DATE # + --sample-size N si besoin7. classify-ttc (depuis classify-ttc/)
cd classify-ttc && uv sync --locked
uv run main.py predict-basic \
--model "mlflow-artifacts:/10/cacf2603514b4887bbfb77e2654c9bc1/artifacts/model" \
--file "$RUN_ROOT/classify-regex/raw_test_without_regex.parquet" \
--output "$RUN_ROOT/classify-ttc/predictions.parquet" \
--text-column l_pr_product --top-k 10L’URI du modèle est le paramètre de workflow classify-ttc-model-uri (cf. argo/codif-pipeline.yaml).
8. reconcile-llm
cd reconcile-llm && uv sync --locked
uv run main.py reconcile-llm \
--lcs-file "$RUN_ROOT/classify-lcs/raw_test_LCS.parquet" \
--rag-file "$RUN_ROOT/classify-rag-notices/predictions.parquet" \
--rag-annotations-file "$RUN_ROOT/classify-rag-annotations/predictions.parquet" \
--ttc-file "$RUN_ROOT/classify-ttc/predictions.parquet" \
--mapping-file "$RUN_ROOT/prune-codes/mapping_lvl4.parquet" \
--output-file "$RUN_ROOT/reconcile-llm/predictions.parquet" \
--extra-columns-file "$RUN_ROOT/build-datasets/observations.parquet" \
--model gemma4-26b-moe --concurrency 5Sans --mapping-file, les codes ne sont pas normalisés (troncature + élagage) — le pipeline Argo le passe toujours.
Si --output-file existe déjà, l’étape reprend automatiquement les observations non traitées (checkpoint toutes les 50 observations) — relancer la même commande suffit après une interruption.
Pour déboguer une seule observation (affiche le prompt et la décision, n’écrit rien) :
uv run main.py reconcile-llm --id <uuid-de-la-ligne> --output json \
--model gemma4-26b-moe \
--lcs-file "$RUN_ROOT/classify-lcs/raw_test_LCS.parquet" \
--rag-file "$RUN_ROOT/classify-rag-notices/predictions.parquet" \
--rag-annotations-file "$RUN_ROOT/classify-rag-annotations/predictions.parquet" \
--ttc-file "$RUN_ROOT/classify-ttc/predictions.parquet" \
--mapping-file "$RUN_ROOT/prune-codes/mapping_lvl4.parquet"8bis. reconcile-sirus (depuis reconcile-sirus/)
Alternative au juge LLM (cf. 8bis. SIRUS) : ne tourne dans le pipeline que si reconciliation: sirus. Python pur, aucun besoin de R.
cd reconcile-sirus && uv sync --locked
uv run main.py predict \
--lcs-file "$RUN_ROOT/classify-lcs/raw_test_LCS.parquet" \
--rag-file "$RUN_ROOT/classify-rag-notices/predictions.parquet" \
--rag-annotations-file "$RUN_ROOT/classify-rag-annotations/predictions.parquet" \
--ttc-file "$RUN_ROOT/classify-ttc/predictions.parquet" \
--mapping-file "$RUN_ROOT/prune-codes/mapping_lvl4.parquet" \
--tocodify-file "$RUN_ROOT/classify-regex/raw_test_without_regex.parquet" \
--extra-columns-file "$RUN_ROOT/build-datasets/observations.parquet" \
--model-uri "mlflow-artifacts:/12/9f3c.../artifacts/model" \
--output-file "$RUN_ROOT/reconcile-sirus/predictions.parquet"L’URI du modèle est le paramètre de workflow reconcile-sirus-model-uri ; un chemin local vers un dossier contenant rules.json est aussi accepté, ce qui permet de tester sans MLflow.
9. export-results
cd export-results && uv sync --locked
uv run main.py --run-id $RUN_ID --run-date $RUN_DATE \
--input-file s3://projet-budget-famille/data/workflow_inputs/mon_fichier.csv \
--text-column NAT_DEP --shop-column MAG_DEP --budget-column MONT_DEP(--input-file est obligatoire : il restaure le nom du fichier livré.)
10. report
cd report && uv sync --locked
uv run main.py --run-id $RUN_ID --run-date $RUN_DATE \
--experiment-name codif-coicop-evalSans MLFLOW_TRACKING_URI, le log MLflow est simplement sauté.
11. evaluate (facultative)
cd evaluate && uv sync --locked
uv run main.py --run-id $RUN_ID --run-date $RUN_DATE \
--input-file s3://projet-budget-famille/data/workflow_inputs/mon_fichier.csv \
--reconciliation llm --experiment-name codif-coicop-eval \
--source-column source # facultatif : ventilation par provenanceElle n’a de sens que sur un run dont le fichier d’entrée portait une colonne d’étiquettes (--label-column de build-datasets). Sans quoi elle échoue en annonçant que la vérité canonique code_lvl4 est absente. Voir 11. Evaluate.
Entraîner le modèle SIRUS (hors pipeline)
L’entraînement n’est pas une étape du pipeline : il produit un modèle destiné à un run futur, et reconcile-sirus-model-uri doit être connue au argo submit. Il se lance donc à la main, comme celui de TTC, sur un run passé étiqueté (soumis avec label-column) — l’ajustement a besoin d’une vérité terrain.
cd reconcile-sirus/
./train.sh 2026-06-29/codif-vvkv9Réglages optionnels, par variables d’environnement (les défauts conviennent dans la quasi-totalité des cas) : NUM_RULE, MAX_DEPTH, SEED, EXPERIMENT.
Le script enchaîne trois étapes : construction de la table candidat-level en Python, ajustement en R, mesures et log MLflow en Python. Il vérifie au préalable que MLFLOW_TRACKING_URI est définie — seul échec qui n’arriverait qu’au dernier pas, après plusieurs minutes d’ajustement. La première exécution compile le paquet sirus depuis la source d’archive CRAN (~1 min 30) : il a été archivé et n’existe plus en binaire.
Il termine en affichant l’URI à recopier dans argo/params.yaml :
Modèle SIRUS loggué. Pour l'utiliser, mettre dans argo/params.yaml :
reconcile-sirus-model-uri: mlflow-artifacts:/12/9f3c.../artifacts/model
Les artefacts restent dans --artifacts-dir. Si le log MLflow échoue (réseau, URI mal réglée), relancer la 3ᵉ étape seule suffit — inutile de réajuster le modèle :
uv run main.py finalize --artifacts-dir artifacts/codif-vvkv9 \
--experiment codif-coicop-sirus --run-id codif-vvkv9 --run-date 2026-06-29Déboguer un run
Logs des étapes. Chaque étape tourne dans son pod :
argo list # retrouver le workflow
argo get <nom-du-workflow> # statut du DAG, étape par étape
argo logs <nom-du-workflow> # logs agrégés (ou --container main, -f pour suivre)Inspecter les fichiers intermédiaires sur S3. Toutes les sorties d’étapes sont des parquet sous $RUN_ROOT/<étape>/. DuckDB permet de les interroger directement :
import duckdb, os
con = duckdb.connect()
con.execute(f"""
CREATE SECRET (TYPE S3,
KEY_ID '{os.environ["AWS_ACCESS_KEY_ID"]}',
SECRET '{os.environ["AWS_SECRET_ACCESS_KEY"]}',
SESSION_TOKEN '{os.environ["AWS_SESSION_TOKEN"]}',
ENDPOINT '{os.environ["AWS_S3_ENDPOINT"]}',
URL_STYLE 'path');
""")
con.sql(f"SELECT * FROM read_parquet('{RUN_ROOT}/reconcile-llm/predictions.parquet') LIMIT 10")Comprendre un code manquant. Dans le fichier livré, predicted_code vide signifie « ni regex, ni LLM ». La raison de l’échec LLM est dans la colonne llm_error de $RUN_ROOT/reconcile-llm/predictions.parquet (cf. 8. Decide).
Tracer les appels LLM. Les appels de classify-rag-notices et classify-rag-annotations sont tracés dans Langfuse (prompt, réponse, latence par produit). Les métriques de chaque branche RAG vont dans MLflow (expériences test et rag-annotation, avec les tags de provenance de l’index : index.git_sha, index.run_id), et les paramètres/métriques du run complet dans l’expérience codif-coicop-eval via l’étape report, avec le report.html en artefact.
Retour à l’accueil