Mode solo
Menez seul une chaîne d'annotation complète dans Potato : un flux de travail LLM-humain en 12 phases, de l'amorçage à l'étiquetage, l'arbitrage et l'affinage, sans équipe d'annotation.
Nouveau dans la v2.3.0
Un projet d'annotation classique demande plusieurs annotateurs, un calcul d'accord inter-annotateurs, des tours d'arbitrage et beaucoup de coordination. Pour beaucoup d'équipes de recherche, c'est là que se situe le goulot d'étranglement : pas dans l'interface d'annotation, mais dans la logistique du recrutement, de la formation et de la gestion d'une équipe.
Le mode solo remplace le paradigme multi-annotateurs par un seul expert humain qui collabore avec un LLM. L'humain fournit des étiquettes de qualité sur un petit sous-ensemble choisi avec soin. Le LLM apprend de ces étiquettes, propose des étiquettes pour le reste, et l'humain ne revoit que les cas où le LLM est incertain ou probablement dans l'erreur. Un flux de travail en 12 phases orchestre le tout automatiquement.
Sur nos jeux de tests internes, le mode solo atteint plus de 95 % d'accord avec une chaîne multi-annotateurs complète, pour seulement 10 à 15 % des étiquettes humaines.
Le flux de travail en 12 phases
Le mode solo se déroule en 12 phases. Le système passe de l'une à l'autre automatiquement selon des seuils configurables, mais vous pouvez aussi déclencher les transitions à la main depuis le tableau de bord d'administration.
Phase 1 : annotation d'amorçage
L'annotateur humain étiquette un jeu d'amorçage initial. Potato sélectionne des instances variées et représentatives par regroupement sur les plongements, de façon à couvrir au mieux la distribution des données.
Taille d'amorçage par défaut : 50 instances (configurable via seed_count)
Phase 2 : calibrage initial du LLM
Le LLM reçoit les annotations d'amorçage comme exemples few-shot et étiquette un lot de calibrage. Potato compare ses prédictions aux étiquettes d'amorçage mises de côté pour établir une justesse de référence.
Phase 3 : analyse des confusions
Potato repère les désaccords systématiques entre l'humain et le LLM. Il construit une matrice de confusion et fait ressortir les types d'erreur les plus fréquents (par exemple « le LLM étiquette neutre en positif dans 40 % des cas »).
Phase 4 : affinage des consignes
À partir de l'analyse des confusions, Potato produit des consignes d'annotation affinées pour le LLM. L'humain les relit et les modifie avant application. C'est une étape interactive : l'annotateur peut ajouter des exemples, clarifier des cas limites et ajuster la définition des étiquettes.
Phase 5 : génération de fonctions d'étiquetage
Inspiré du cadre ALCHEmist, Potato génère des fonctions d'étiquetage programmatiques à partir des annotations existantes. Ce sont des règles simples fondées sur des motifs (par exemple « si le texte contient excellent sans négation, étiqueter en positif ») qui étiquettent les instances faciles avec une précision élevée, et réservent l'effort humain et celui du LLM aux cas difficiles.
Phase 6 : étiquetage actif
L'humain étiquette d'autres instances, choisies par apprentissage actif. Potato donne la priorité aux instances où le LLM est le plus incertain, où les fonctions d'étiquetage se contredisent, ou qui sont éloignées des exemples d'apprentissage existants dans l'espace des plongements.
Phase 7 : boucle d'affinage automatique
Le LLM réétiquette tout le jeu de données avec les consignes et les exemples few-shot à jour. Potato compare le résultat à l'ensemble des étiquettes humaines et déclenche un nouveau cycle d'analyse des confusions et d'affinage des consignes si la justesse reste sous le seuil.
Phase 8 : exploration des désaccords
L'humain passe en revue toutes les instances où le LLM et les fonctions d'étiquetage divergent. Ce sont en général les exemples les plus instructifs et les plus difficiles. Les étiquettes humaines sur ces cas ont la valeur marginale la plus élevée.
Phase 9 : synthèse de cas limites
Potato demande au LLM de générer des cas limites synthétiques à partir des motifs de confusion identifiés. L'humain étiquette ces exemples synthétiques, qui sont ensuite ajoutés au contexte du LLM pour améliorer ses résultats sur les cas les plus durs.
Phase 10 : escalade en cascade par confiance
Le LLM attribue un score de confiance à chaque instance encore non étiquetée. Les instances remontent vers l'humain par difficulté décroissante (confiance croissante). L'humain étiquette jusqu'à ce que les indicateurs de qualité se stabilisent.
Phase 11 : optimisation de l'invite
Inspiré de DSPy, Potato lance une optimisation automatique de l'invite en utilisant les étiquettes humaines accumulées comme jeu de validation. Il essaie plusieurs variantes (formulation de l'instruction, ordre des exemples, chaîne de pensée ou réponse directe) et retient la plus performante.
Phase 12 : validation finale
L'humain relit un échantillon aléatoire des instances étiquetées par le LLM. Si la justesse atteint le seuil, le jeu de données est terminé. Sinon, le système repart à la phase 6.
Configuration
Démarrage rapide
Une configuration minimale du mode solo :
task_name: "Sentiment Classification"
task_dir: "."
data_files:
- "data/reviews.jsonl"
item_properties:
id_key: id
text_key: text
solo_mode:
enabled: true
# LLM provider
llm:
endpoint_type: openai
model: "gpt-4o"
api_key: ${OPENAI_API_KEY}
# Basic thresholds
seed_count: 50
accuracy_threshold: 0.92
confidence_threshold: 0.85
annotation_schemes:
- annotation_type: radio
name: sentiment
labels:
- Positive
- Neutral
- Negative
output_annotation_dir: "output/"
output_annotation_format: "jsonl"Référence complète de configuration
solo_mode:
enabled: true
# LLM configuration
llm:
endpoint_type: openai # openai, anthropic, ollama, vllm
model: "gpt-4o"
api_key: ${OPENAI_API_KEY}
temperature: 0.1 # low temperature for consistency
max_tokens: 256
# Phase control
phases:
seed:
count: 50 # number of seed instances
selection: diversity # diversity, random, or stratified
embedding_model: "all-MiniLM-L6-v2"
calibration:
batch_size: 100
holdout_fraction: 0.2 # fraction of seed used for validation
confusion_analysis:
min_samples: 30
significance_threshold: 0.05
guideline_refinement:
auto_suggest: true # LLM suggests guideline edits
require_approval: true # human must approve changes
labeling_functions:
enabled: true
max_functions: 20
min_precision: 0.90 # only keep high-precision rules
min_coverage: 0.01 # must cover at least 1% of data
active_labeling:
batch_size: 25
strategy: uncertainty # uncertainty, diversity, or hybrid
max_batches: 10
refinement_loop:
max_iterations: 3
improvement_threshold: 0.02
disagreement_exploration:
max_instances: 200
sort_by: confidence_gap
edge_case_synthesis:
enabled: true
count: 50
diversity_weight: 0.3
confidence_escalation:
escalation_budget: 200 # max instances to escalate
batch_size: 25
stop_when_stable: true # stop if last batch accuracy is 100%
prompt_optimization:
enabled: true
candidates: 10 # number of prompt variants to try
metric: f1_macro
search_strategy: bayesian # bayesian, grid, or random
final_validation:
sample_size: 100
min_accuracy: 0.92
fallback_phase: 6 # go back to Phase 6 if validation fails
# Instance prioritization across phases
prioritization:
pools:
- name: uncertain
weight: 0.30
description: "LLM confidence below threshold"
- name: disagreement
weight: 0.25
description: "LLM and labeling functions disagree"
- name: boundary
weight: 0.20
description: "Near decision boundary in embedding space"
- name: novel
weight: 0.10
description: "Far from all existing labeled examples"
- name: error_pattern
weight: 0.10
description: "Matches known confusion patterns"
- name: random
weight: 0.05
description: "Random sample for calibration"Principales capacités
Analyse des confusions
Après chaque tour d'étiquetage, Potato construit une matrice de confusion entre les étiquettes humaines et celles du LLM. Le tableau de bord d'administration affiche :
- Précision, rappel et F1 par classe, du point de vue du LLM
- Les couples de confusion les plus fréquents (par exemple « neutre classé en positif : 23 instances »)
- Des instances d'exemple pour chaque couple de confusion
- Des courbes de tendance montrant les progrès au fil des tours d'affinage
Pour y accéder par programme :
python -m potato.solo confusion --config config.yamlSortie :
Confusion Analysis (Round 2)
============================
Overall Accuracy: 0.87 (target: 0.92)
Top Confusion Pairs:
neutral -> positive: 23 instances (15.3%)
negative -> neutral: 11 instances (7.3%)
positive -> neutral: 5 instances (3.3%)
Per-Class Performance:
Positive: P=0.91 R=0.94 F1=0.92
Neutral: P=0.78 R=0.71 F1=0.74
Negative: P=0.93 R=0.88 F1=0.90
Boucle d'affinage automatique
La boucle d'affinage alterne entre étiquetage par le LLM, analyse des confusions et mise à jour des consignes. À chaque itération :
- Le LLM étiquette tout le jeu de données avec les consignes en vigueur
- Potato compare le résultat à toutes les étiquettes humaines disponibles
- Si la justesse est sous le seuil, l'analyse des confusions se lance
- Le LLM propose des modifications de consignes fondées sur les motifs d'erreur
- L'humain relit et approuve les modifications
- Le cycle recommence (jusqu'à
max_iterations)
solo_mode:
llm:
endpoint_type: anthropic
model: "claude-sonnet-4-20250514"
api_key: ${ANTHROPIC_API_KEY}
phases:
refinement_loop:
max_iterations: 3
improvement_threshold: 0.02 # stop if improvement is less than 2%Fonctions d'étiquetage (inspirées d'ALCHEmist)
Potato génère des fonctions d'étiquetage légères à partir des motifs observés dans les annotations humaines. Ce ne sont pas des appels au LLM : ce sont des règles rapides et déterministes.
Exemple de fonctions d'étiquetage générées :
# Auto-generated labeling function 1
# Precision: 0.96, Coverage: 0.08
def lf_strong_positive_words(text):
positive = {"excellent", "amazing", "fantastic", "outstanding", "perfect"}
if any(w in text.lower() for w in positive):
if not any(neg in text.lower() for neg in {"not", "never", "no"}):
return "Positive"
return None # abstain
# Auto-generated labeling function 2
# Precision: 0.93, Coverage: 0.05
def lf_explicit_negative(text):
negative = {"terrible", "awful", "horrible", "worst", "disgusting"}
if any(w in text.lower() for w in negative):
return "Negative"
return NoneRégler le comportement des fonctions d'étiquetage :
solo_mode:
phases:
labeling_functions:
enabled: true
max_functions: 20
min_precision: 0.90
min_coverage: 0.01
types:
- keyword_match
- regex_pattern
- length_threshold
- embedding_clusterExplorateur de désaccords
L'explorateur de désaccords présente les instances où les différents signaux se contredisent. Pour chacune, l'annotateur voit :
- L'étiquette prédite par le LLM et sa confiance
- Les votes des fonctions d'étiquetage, s'il y en a
- Les voisins étiquetés les plus proches dans l'espace des plongements
- Le texte ou contenu brut
C'est l'activité d'annotation la plus rentable : chaque étiquette lève une ambiguïté réelle.
solo_mode:
phases:
disagreement_exploration:
max_instances: 200
sort_by: confidence_gap # or "lf_disagreement" or "random"
show_llm_reasoning: true # display LLM's chain-of-thought
show_nearest_neighbors: 3 # show 3 nearest labeled examplesEscalade en cascade par confiance
Une fois le gros du jeu de données étiqueté par le LLM, Potato classe toutes les instances étiquetées par confiance et fait remonter les moins sûres vers l'humain. Cela continue par lots jusqu'à ce que la qualité se stabilise.
solo_mode:
phases:
confidence_escalation:
escalation_budget: 200
batch_size: 25
stop_when_stable: true
stability_window: 3 # stop if last 3 batches are all correctPriorisation des instances sur plusieurs signaux
Dans toutes les phases qui font intervenir l'étiquetage humain, Potato utilise un système de réservoirs pondérés pour choisir les instances les plus informatives. Six réservoirs alimentent une file de priorité unique :
solo_mode:
prioritization:
pools:
- name: uncertain
weight: 0.30
- name: disagreement
weight: 0.25
- name: boundary
weight: 0.20
- name: novel
weight: 0.10
- name: error_pattern
weight: 0.10
- name: random
weight: 0.05- uncertain : instances où la confiance du LLM est sous
confidence_threshold - disagreement : instances où le LLM et les fonctions d'étiquetage donnent des étiquettes différentes
- boundary : instances proches de la frontière de décision dans l'espace des plongements
- novel : instances éloignées de tout exemple étiqueté existant
- error_pattern : instances qui correspondent à des motifs de confusion connus des tours précédents
- random : un petit échantillon aléatoire pour maintenir le calibrage et repérer les angles morts
Synthèse de cas limites
Potato se sert du LLM pour générer des exemples synthétiques qui visent des faiblesses connues :
solo_mode:
phases:
edge_case_synthesis:
enabled: true
count: 50
diversity_weight: 0.3
confusion_pairs: # focus on these error types
- ["neutral", "positive"]
- ["negative", "neutral"]Le LLM produit des exemples ambigus entre les couples d'étiquettes indiqués. L'humain les étiquette, et ces étiquettes rejoignent le contexte few-shot des tours d'étiquetage suivants.
Optimisation de l'invite (inspirée de DSPy)
En phase 11, Potato lance une optimisation automatique de l'invite pour trouver le meilleur format d'instruction pour le LLM :
solo_mode:
phases:
prompt_optimization:
enabled: true
candidates: 10
metric: f1_macro
search_strategy: bayesian
variations:
- instruction_style # formal vs. conversational
- example_ordering # random, by-class, by-difficulty
- reasoning_mode # direct, chain-of-thought, self-consistency
- example_count # 3, 5, 10, 15 few-shot examplesSuivre l'avancement
Le tableau de bord d'administration montre l'avancement du mode solo en temps réel :
- Phase en cours et progression à l'intérieur de chaque phase
- Étiquettes humaines réalisées par rapport au budget total
- Justesse du LLM au fil du temps (par tour)
- Couverture et précision des fonctions d'étiquetage
- Histogramme de la distribution des confiances
- Temps restant estimé
Depuis la ligne de commande :
python -m potato.solo status --config config.yamlSolo Mode Status
================
Current Phase: 6 (Active Labeling) - Batch 3/10
Human Labels: 142 / ~300 estimated total
LLM Accuracy: 0.89 (target: 0.92)
LF Coverage: 0.23 (labeling functions cover 23% of data)
Dataset Size: 10,000 instances
- Human labeled: 142
- LF labeled: 2,300
- LLM labeled: 7,558
- Unlabeled: 0
Mode solo ou multi-annotateurs classique ?
Choisissez le mode solo quand :
- Vous disposez d'un expert du domaine capable de fournir des étiquettes de qualité
- Le budget ou la logistique interdisent de recruter plusieurs annotateurs
- La tâche a des catégories claires et bien définies
- Vous devez étiqueter un gros jeu de données (plus de 1 000 instances)
- La rapidité compte plus que la mesure de l'accord inter-annotateurs
Choisissez le multi-annotateurs classique quand :
- Il vous faut des statistiques d'accord inter-annotateurs pour une publication
- La tâche est très subjective (offense, humour, etc.)
- Vous voulez étudier les motifs de désaccord entre annotateurs
- La réglementation impose plusieurs annotateurs indépendants
- L'espace d'étiquettes est complexe ou mouvant (les consignes d'annotation sont encore en cours d'élaboration)
Approche hybride : utilisez le mode solo pour le gros de l'étiquetage, puis confiez un échantillon aléatoire de 10 à 20 % à un second annotateur pour calculer les statistiques d'accord. Vous gardez l'efficacité du mode solo avec la garantie de qualité d'une vérification multi-annotateurs.
solo_mode:
enabled: true
# ... solo mode config ...
# Hybrid: assign verification sample to second annotator
verification:
enabled: true
sample_fraction: 0.15
annotator: "reviewer_1"Pour aller plus loin
- Tutoriel du mode solo : étiqueter 10 000 exemples -- pas à pas
- Apprentissage actif -- le système d'apprentissage actif sous-jacent
- Support IA -- configuration de l'intégration des LLM
- Contrôle de la qualité -- assurance qualité des annotations
- MACE -- estimation de compétence (utile pour la vérification en mode hybride)
Pour les détails d'implémentation, consultez la documentation source.