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 标签之间构建混淆矩阵。管理面板显示:

  • 从 LLM 角度看的每类精确率、召回率和 F1
  • 最常见的混淆对(例如"中性被误分为正面: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 按置信度对所有 LLM 标注的实例排序,把置信度最低的升级给人类。这个过程按批次持续进行,直到质量趋于稳定。

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 在 Potato 进程内用 CPU 运行 Laya,需要 pip install 'potato-annotation[decision]';systemone 则连接任何提供 /v1/systemone API 的服务器,例如托管的 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 方案;如果把决策模型设为标注 multiselect、片段或自由文本方案,Solo Mode 会拒绝启动。在评分量表上,Laya 的分数几乎跟不上量表,所以在让 likert 或 slider 方案经过它之前,先用 Try on 10 items 检查几个条目。置信度是模型给它所选选项的概率,它的校准程度因领域而异,所以请根据你自己的数据设置该层的阈值。config-decision-model.yaml 是一个可以直接运行的版本。

盲态建议

默认情况下,标注页面会在你作答之前显示 LLM 的标签、它的置信度和一个 Accept 按钮。这样很快,也会把你的答案拉向建议。当你与 LLM 的一致性超过 end_human_annotation_agreement 时,Solo Mode 会把剩余语料交给 LLM,所以如果一致性是在你看过建议之后给出的标签上测得的,你那部分工作就可能过早结束。从 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 生成在指定标签对之间存在歧义的示例。人类标注这些示例,这些标签随后被加入后续 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 模块,也没有 Solo Mode 的 CLI:整个工作流都在服务器自己的路由背后,因此进度是从面板读取的。它报告的内容是:

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 的情况:

  • 你有一位能提供高质量标签的领域专家
  • 预算或后勤条件不允许雇用多名标注者
  • 任务的类别清晰、定义明确
  • 你需要标注大型数据集(1,000 个以上实例)
  • 速度比测量标注者间一致性更重要

适合使用传统多标注者的情况:

  • 你需要标注者间一致性统计用于发表
  • 任务高度主观(例如冒犯性、幽默感)
  • 你需要研究标注者的分歧模式
  • 法规要求多名独立标注者
  • 标签空间复杂或仍在演变(标注指南还在制定中)

混合做法: 先用 Solo Mode 完成初始的批量标注,再指派第二位标注者标注随机的 10-20% 样本来计算一致性统计。这样既有 Solo Mode 的效率,又有多标注者验证带来的质量保证。

Solo Mode 自己没有验证块。把第二位标注者当作一次普通的 Potato 流程跑在导出的标签上,然后像对待任意两位标注者那样,计算他们的标签与 Solo Mode 标签之间的一致性。(icl_labeling 确实有 verification 块,但那属于上下文内标注,不属于 Solo Mode。)


延伸阅读

有关实现详情,请参阅源文档。