Skip to content

Solo Mode

Полный конвейер разметки в Potato силами одного человека: эксперт и LLM проходят конечный автомат фаз от промпта и параллельной разметки до итоговой проверки.

Новое в v2.3.0

Обычный проект разметки требует нескольких разметчиков, расчёта согласованности между ними, раундов арбитража и немалых усилий на координацию. Для многих исследовательских групп именно это главное узкое место: не интерфейс разметки, а организационная работа по найму, обучению и управлению командой.

Solo Mode заменяет схему с несколькими разметчиками совместной работой одного эксперта и LLM. Человек даёт качественные метки на небольшом, специально отобранном подмножестве. LLM учится на этих метках, предлагает метки для остальных объектов, а человек проверяет только те случаи, где LLM не уверена или, вероятно, ошибается. Всем этим управляет конечный автомат фаз: он переходит дальше по заданным вами порогам и возвращается к промпту, когда оказывается, что ошибка именно в промпте.

Во внутренних тестах Solo Mode достигал согласия 95%+ с полными конвейерами из нескольких разметчиков, требуя при этом лишь от 10 до 15% от общего числа человеческих меток.

Порядок работы

Solo Mode устроен как конечный автомат. SoloPhaseController отслеживает, в какой фазе находится проект и в какие фазы ему разрешено перейти дальше, поэтому маршрут не прямой: несколько фаз могут вернуться назад, если промпт ещё требует доработки.

Настройка

Описание задачи и данные. Кнопка Generate prompt просит модель ревизии (revision_models) написать первый промпт по описанию и схемам разметки. Если модели ревизии нет или она ничего не вернула, первым промптом становится само описание, и в истории промптов это отмечается. Из настройки можно перейти только к проверке промпта.

Проверка промпта

Potato составляет промпт по описанию задачи, а вы его редактируете. Сюда возвращаются несколько последующих фаз, когда выясняется, что проблема в промпте, поэтому эта фаза служит узлом всего процесса, а не разовым шагом.

Отсюда проект идёт либо через цикл пограничных случаев, либо сразу к параллельной разметке.

Синтез пограничных случаев, затем их разметка

LLM генерирует примеры на границах классов, а вы их размечаете. Из разметки можно вернуться к проверке промпта, если примеры показывают, что промпт неверен.

Валидация промпта

Ваши метки на пограничных случаях сравниваются с тем, что выдаёт промпт. Дальше проект либо переходит к параллельной разметке, либо возвращается к проверке промпта.

Параллельная разметка

Вы и LLM размечаете одни и те же данные одновременно. Именно эта фаза даёт показатель согласия, от которого зависит всё остальное.

Разрешение разногласий

Там, где ваши метки и метки LLM расходятся, решение принимаете вы. Отсюда проект возвращается к параллельной разметке или к проверке промпта, если разногласия указывают на промпт, а не на отдельные объекты.

Активная разметка

Основная фаза разметки, в которой объекты выбираются взвешенными пулами из instance_selection. Отсюда Potato может перейти к периодической проверке, к проверке правил или дальше, к автономной разметке.

Периодическая проверка

Метки LLM с низкой уверенностью, которые показываются каждые thresholds.periodic_review_interval объектов. Отсюда проект возвращается к активной разметке, переходит к проверке правил или откатывается к проверке промпта.

Проверка правил

Правила для пограничных случаев, которые Potato извлёк из вашей разметки, выносятся вам на утверждение. Отсюда проект возвращается к активной разметке или к проверке промпта.

Автономная разметка

LLM размечает всё, что осталось. Единственный выход из этой фазы ведёт к итоговой валидации.

Итоговая валидация

Вы проверяете выборку меток, поставленных только LLM. Если порог thresholds.end_human_annotation_agreement пройден, проект завершается; если нет, он возвращается к активной разметке.

Завершено

Конечное состояние.


Конфигурация

Быстрый старт

Минимальная конфигурация Solo Mode:

yaml
annotation_task_name: "Sentiment Classification"
task_dir: "."
 
data_files:
  - "data/reviews.jsonl"
 
item_properties:
  id_key: id
  text_key: text
 
solo_mode:
  enabled: true
 
  # The models that label. A list: the first is used, the rest are fallbacks.
  labeling_models:
    - endpoint_type: openai
      model: "gpt-4o"
      api_key: ${OPENAI_API_KEY}
      temperature: 0.1
      max_tokens: 1024
 
  # When the project may stop asking the human
  thresholds:
    end_human_annotation_agreement: 0.92
    minimum_validation_sample: 50
    confidence_low: 0.5
 
  state_dir: "solo_state_sentiment"
 
annotation_schemes:
  - annotation_type: radio
    name: sentiment
    description: "What is the sentiment of this text?"
    labels:
      - Positive
      - Neutral
      - Negative
 
output_annotation_dir: "output/"
export_annotation_format: "jsonl"

Полный справочник по конфигурации

yaml
solo_mode:
  enabled: true
 
  # Models that label, and models that revise. Both are lists; revision_models
  # defaults to labeling_models when omitted.
  labeling_models:
    - endpoint_type: openai       # openai, anthropic, ollama, vllm, ...
      model: "gpt-4o"
      api_key: ${OPENAI_API_KEY}
      temperature: 0.1
      max_tokens: 1024
  revision_models:
    - endpoint_type: anthropic
      model: "claude-sonnet-4-20250514"
      api_key: ${ANTHROPIC_API_KEY}
      max_tokens: 1024
 
  # Sentence embeddings, used for diversity and neighbour lookups
  embedding:
    model_name: "all-MiniLM-L6-v2"
 
  # How the LLM's uncertainty is measured
  uncertainty:
    strategy: direct_confidence
    sampling_diversity:
      num_samples: 5
      temperature: 1.0
 
  # When the human can stop, and what counts as agreement per scheme type
  thresholds:
    end_human_annotation_agreement: 0.90
    minimum_validation_sample: 50
    confidence_low: 0.5
    confidence_high: 0.8
    periodic_review_interval: 100
    likert_tolerance: 1
    multiselect_jaccard_threshold: 0.5
    textbox_embedding_threshold: 0.7
    span_overlap_threshold: 0.5
 
  # Weights for the pools that choose what the human sees next
  instance_selection:
    low_confidence_weight: 0.3
    disagreement_weight: 0.1
    diversity_weight: 0.2
    llm_predicted_weight: 0.3
    random_weight: 0.1
    cartography_weight: 0.0
    edge_case_rule_weight: 0.0
 
  batches:
    llm_labeling_batch: 50
    max_parallel_labels: 200
 
  labeling_functions:
    enabled: true
    auto_extract: true
    max_functions: 50
    min_confidence: 0.85
    min_coverage: 3               # instances, not a fraction
    vote_threshold: 0.5
 
  edge_case_rules:
    enabled: true
    auto_extract_on_labeling: true
    confidence_threshold: 0.75
    min_rules_for_clustering: 10
    target_cluster_size: 15
    reannotation_enabled: true
    reannotation_confidence_threshold: 0.60
    max_reannotations_per_instance: 2
 
  confusion_analysis:
    enabled: true
    min_instances_for_pattern: 3
    max_patterns: 20
    auto_suggest_guidelines: false
 
  prompt_optimization:
    enabled: true
    find_smallest_model: true
    target_accuracy: 0.85
    optimization_interval_seconds: 300
    accuracy_weight: 0.7
    length_weight: 0.2
 
  # Send cheap cases to a small model and hard ones to a larger one
  confidence_routing:
    enabled: false
    tiers:
      - name: cheap
        confidence_threshold: 0.8
        model:
          endpoint_type: ollama
          model: "llama3.2"
 
  refinement_loop:
    enabled: true
    trigger_interval: 50
    min_improvement: 0.02
    max_cycles: 5
    patience: 2
 
  state_dir: "solo_state_sentiment"

Основные возможности

Анализ ошибок

После каждого раунда разметки Potato строит матрицу ошибок между метками человека и LLM. Админ-панель показывает:

  • точность, полноту и F1 по каждому классу с точки зрения LLM;
  • самые частые пары путаницы (например, «neutral ошибочно отнесён к positive: 23 объекта»);
  • примеры объектов для каждой пары путаницы;
  • графики динамики, показывающие улучшение от раунда к раунду уточнения.

У Solo Mode нет интерфейса командной строки. Он работает внутри сервера и управляется со своих страниц запущенного экземпляра, поэтому результаты анализа читаются с админ-панели, а не из CLI. Отчёт выглядит так:

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

Автоматический цикл уточнения

Цикл уточнения чередует разметку LLM, анализ ошибок и обновление инструкций. На каждой итерации:

  1. LLM размечает весь набор данных по текущим инструкциям.
  2. Potato сравнивает результат со всеми доступными человеческими метками.
  3. Если точность ниже порога, запускается анализ ошибок.
  4. LLM предлагает правки инструкций на основе закономерностей в ошибках.
  5. Человек просматривает и утверждает правки.
  6. Цикл повторяется (не более max_cycles раз).
yaml
solo_mode:
  revision_models:
    - endpoint_type: anthropic
      model: "claude-sonnet-4-20250514"
      api_key: ${ANTHROPIC_API_KEY}
 
  refinement_loop:
    enabled: true
    trigger_interval: 50           # run a cycle every 50 new human labels
    min_improvement: 0.02          # stop if a cycle gains less than 2%
    max_cycles: 5
    patience: 2                    # cycles below min_improvement before stopping

Функции разметки (по мотивам ALCHEmist)

Potato генерирует лёгкие функции разметки из закономерностей, замеченных в человеческой разметке. Это не вызовы LLM, а быстрые детерминированные правила.

Пример сгенерированных функций разметки:

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

Настройка поведения функций разметки:

yaml
solo_mode:
  labeling_functions:
    enabled: true
    auto_extract: true
    max_functions: 50
    min_confidence: 0.85
    min_coverage: 3          # minimum instances a function must cover
    vote_threshold: 0.5      # agreement needed when functions disagree

Обозреватель разногласий

Обозреватель разногласий показывает объекты, на которых разные сигналы противоречат друг другу. Для каждого объекта разметчик видит:

  • метку, предсказанную LLM, и её уверенность;
  • голоса функций разметки (если они есть);
  • ближайших размеченных соседей в пространстве эмбеддингов;
  • исходный текст или контент.

Это самая ценная работа по разметке: каждая метка разрешает настоящую неоднозначность.

yaml
solo_mode:
  # Disagreement is a phase, not a configurable block. What you control is how
  # heavily disagreement weighs when Potato picks the next instance.
  instance_selection:
    disagreement_weight: 0.3
    low_confidence_weight: 0.3
    diversity_weight: 0.2
    llm_predicted_weight: 0.1
    random_weight: 0.1

Каскадная эскалация по уверенности

Когда основную часть набора данных разметила LLM, Potato упорядочивает все размеченные ею объекты по уверенности и передаёт человеку наименее уверенные. Это продолжается пакетами, пока качество не стабилизируется.

yaml
solo_mode:
  thresholds:
    confidence_low: 0.5           # below this, the human sees it
    confidence_high: 0.8          # above this, the LLM's label stands
    periodic_review_interval: 100 # surface low-confidence labels this often
 
  batches:
    llm_labeling_batch: 50
    max_parallel_labels: 200

Модели принятия решений

Начиная с Potato 2.11, в Solo Mode размечать может модель принятия решений. Такая модель отвечает на вопрос с фиксированным набором вариантов за один прямой проход и возвращает вероятность для каждого варианта, поэтому размечает быстрее и дешевле, чем LLM, но не может писать промпты, правила или объяснения. К ней ведут два типа эндпоинтов. laya запускает Laya внутри процесса Potato на CPU и требует pip install 'potato-annotation[decision]', а systemone обращается к любому серверу с API /v1/systemone, например к размещённому Jev или к OpenJev.

Обычно модель принятия решений ставят на первый уровень маршрутизации по уверенности: она размечает всё, и только объекты, в которых она не уверена, уходят дальше к LLM:

yaml
solo_mode:
  labeling_models:
    - endpoint_type: laya
      model: convaiinnovations/laya
  revision_models:                     # required: decision models cannot write text
    - endpoint_type: vllm
      model: google/gemma-4-12B-it-qat-w4a16-ct
      base_url: http://localhost:8001/v1
  confidence_routing:
    enabled: true
    tiers:
      - name: laya
        confidence_threshold: 0.9
        model:
          endpoint_type: laya
          model: convaiinnovations/laya
          device: cpu
      - name: llm
        confidence_threshold: 0.5
        model:
          endpoint_type: vllm
          model: google/gemma-4-12B-it-qat-w4a16-ct
          base_url: http://localhost:8001/v1

В revision_models должна стоять генеративная модель. По умолчанию это значение берётся из labeling_models, поэтому в исследовании, где единственная модель является моделью принятия решений, его нужно задать явно. Модели принятия решений размечают схемы radio, select, likert и slider, а Solo Mode не запускается, если такой модели поручено размечать схемы multiselect, спанов или свободного текста. На шкалах оценок баллы Laya едва следуют шкале, поэтому прежде чем направлять через неё схему likert или slider, проверьте несколько объектов кнопкой Try on 10 items. Уверенность равна вероятности, которую модель присвоила выбранному варианту, а её калибровка зависит от предметной области, поэтому порог уровня подбирайте по своим данным. Готовый к запуску вариант лежит в config-decision-model.yaml.

Слепые подсказки

По умолчанию страница разметки показывает метку LLM, её уверенность и кнопку Accept ещё до вашего ответа. Это быстро, но заодно тянет ваш ответ к подсказке. Solo Mode передаёт остаток корпуса LLM, когда ваше согласие с ней превышает end_human_annotation_agreement, поэтому согласие, измеренное на метках, поставленных после того, как вы увидели подсказку, может завершить вашу часть работы слишком рано. Начиная с Potato 2.11, момент появления подсказки задаёт solo_mode.suggestions:

yaml
solo_mode:
  suggestions:
    display: blind_sample      # before_label | after_label | blind_sample
    blind_rate: 0.2
    minimum_blind_sample: 20

blind_sample скрывает подсказку на доле объектов, равной blind_rate, а after_label никогда не показывает её первой. Когда набирается minimum_blind_sample слепых сравнений, при передаче работы с end_human_annotation_agreement сравнивается слепое согласие. Карточка Blind Agreement на панели показывает рядом слепое согласие и согласие с показанной подсказкой, и большой положительный разрыв означает, что вы чаще соглашаетесь с LLM, когда видите её ответ.

Кроме того, в Potato 2.11 Solo Mode проверяет лимит расходов проекта (ai_budget.cap_usd) перед началом разметки и перед каждым пакетом, а также получил кэш ответов (solo_mode.cache), золотой набор с пробными прогонами промптов (solo_mode.gold), проверку чувствительности к промпту, жюри разметки (solo_mode.jury) и упорядочивание по учебной программе (solo_mode.instance_selection.curriculum). Каждая из этих возможностей описана в документе Solo Mode Advanced Features. Когда включён движок разметки, Solo Mode также записывает предсказания своих моделей в хранилище движка, чтобы движок мог их откалибровать и сертифицировать.

Приоритизация объектов по нескольким сигналам

Во всех фазах, где размечает человек, Potato выбирает самые информативные объекты с помощью системы взвешенных пулов. Семь пулов питают единую очередь приоритетов:

yaml
solo_mode:
  instance_selection:
    low_confidence_weight: 0.3
    diversity_weight: 0.2
    llm_predicted_weight: 0.3
    disagreement_weight: 0.1
    random_weight: 0.1
    cartography_weight: 0.0
    edge_case_rule_weight: 0.0
  • low_confidence: уверенность LLM ниже thresholds.confidence_low
  • diversity: объект в пространстве эмбеддингов далёк от того, что вы уже разметили
  • llm_predicted: у LLM есть метка, и её ещё не проверяли
  • disagreement: доступные для объекта сигналы не согласуются
  • random: небольшая случайная выборка, чтобы оценка оставалась честной и находились слепые зоны
  • cartography: сигнал динамики обучения, по умолчанию выключен
  • edge_case_rule: объект подпадает под правило, извлечённое из вашей прежней разметки, по умолчанию выключен

Синтез пограничных случаев

Potato с помощью LLM генерирует синтетические примеры, нацеленные на известные слабые места:

yaml
solo_mode:
  edge_case_rules:
    enabled: true
    auto_extract_on_labeling: true   # derive rules as you label
    confidence_threshold: 0.75
    min_rules_for_clustering: 10
    target_cluster_size: 15
    reannotation_enabled: true       # revisit items a new rule contradicts
    reannotation_confidence_threshold: 0.60
    max_reannotations_per_instance: 2

LLM генерирует примеры, неоднозначные между заданными парами меток. Человек их размечает, и эти метки добавляются в контекст few-shot для следующих раундов разметки LLM.

Оптимизация промпта (по мотивам DSPy)

Potato автоматически оптимизирует промпт, подбирая для LLM лучший формат инструкции, и ищет самую маленькую модель, которая ещё удерживает целевую точность:

yaml
solo_mode:
  prompt_optimization:
    enabled: true
    find_smallest_model: true          # prefer the cheapest model that holds up
    target_accuracy: 0.85
    optimization_interval_seconds: 300
    accuracy_weight: 0.7               # how the candidates are scored
    length_weight: 0.2

Отслеживание прогресса

Админ-панель показывает прогресс Solo Mode в реальном времени:

  • текущую фазу и прогресс внутри каждой фазы;
  • число готовых человеческих меток относительно общего бюджета;
  • точность LLM во времени (по раундам);
  • покрытие и точность функций разметки;
  • гистограмму распределения уверенности;
  • оценку времени до завершения.

Модуля potato.solo и CLI для Solo Mode нет: весь процесс живёт за собственными маршрутами сервера, поэтому прогресс читается с админ-панели. Она сообщает:

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

Когда выбирать Solo Mode, а когда традиционную разметку несколькими разметчиками

Выбирайте Solo Mode, если:

  • у вас есть эксперт в предметной области, способный давать качественные метки;
  • бюджет или организационные условия не позволяют нанять нескольких разметчиков;
  • в задаче ясные, чётко определённые категории;
  • нужно разметить большой набор данных (1000+ объектов);
  • скорость важнее, чем измерение согласованности разметчиков.

Выбирайте традиционную разметку несколькими разметчиками, если:

  • для публикации нужна статистика согласованности разметчиков;
  • задача сильно субъективна (например, оскорбительность, юмор);
  • нужно изучать закономерности разногласий разметчиков;
  • нормативные требования предписывают нескольких независимых разметчиков;
  • пространство меток сложное или меняется (инструкции по разметке ещё разрабатываются).

Гибридный подход: используйте Solo Mode для начальной массовой разметки, а затем поручите второму разметчику случайную выборку от 10 до 20%, чтобы посчитать статистику согласованности. Так вы получаете эффективность Solo Mode вместе с гарантией качества, которую даёт проверка несколькими разметчиками.

Собственного блока проверки у Solo Mode нет. Проведите второго разметчика обычным проходом Potato по экспортированным меткам и посчитайте согласие между его метками и метками Solo Mode так же, как для любых двух разметчиков. (Блок verification есть у icl_labeling, но он относится к разметке в контексте, а не к Solo Mode.)


Дополнительные материалы

Подробности реализации см. в исходной документации.