Skip to content

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 :

yaml
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

yaml
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 :

bash
python -m potato.solo confusion --config config.yaml

Sortie :

text
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 :

  1. Le LLM étiquette tout le jeu de données avec les consignes en vigueur
  2. Potato compare le résultat à toutes les étiquettes humaines disponibles
  3. Si la justesse est sous le seuil, l'analyse des confusions se lance
  4. Le LLM propose des modifications de consignes fondées sur les motifs d'erreur
  5. L'humain relit et approuve les modifications
  6. Le cycle recommence (jusqu'à max_iterations)
yaml
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 :

python
# 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 None

Régler le comportement des fonctions d'étiquetage :

yaml
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_cluster

Explorateur 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.

yaml
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 examples

Escalade 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.

yaml
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 correct

Priorisation 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 :

yaml
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 :

yaml
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 :

yaml
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 examples

Suivre 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 :

bash
python -m potato.solo status --config config.yaml
text
Solo 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.

yaml
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

Pour les détails d'implémentation, consultez la documentation source.