单人模式(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 配置:
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"完整配置参考
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 读取的。它报告的内容大致是这样:
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 标注、混淆分析和指南更新之间迭代。每次迭代:
- LLM 用当前指南标注完整数据集
- Potato 与所有可用的人工标签做对比
- 如果准确率低于阈值,运行混淆分析
- LLM 根据错误模式提出指南修改建议
- 人类审查并批准这些修改
- 循环重复(最多
max_cycles次)
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 调用,而是快速、确定性的规则。
生成的标注函数示例:
# 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配置标注函数的行为:
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 预测的标签和置信度
- 标注函数的投票(如果有)
- 嵌入空间中最近的已标注邻居
- 原始文本或内容
这是价值最高的标注活动:每个标签都解决一处真实的歧义。
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 标注的实例排序,把置信度最低的升级给人类。这个过程按批次持续进行,直到质量趋于稳定。
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:
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/v1revision_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 控制建议何时出现:
solo_mode:
suggestions:
display: blind_sample # before_label | after_label | blind_sample
blind_rate: 0.2
minimum_blind_sample: 20blind_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 用一套加权池系统挑选信息量最大的实例。七个池汇入一个统一的优先级队列:
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 生成针对已知弱点的合成示例:
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: 2LLM 生成在指定标签对之间存在歧义的示例。人类标注这些示例,这些标签随后被加入后续 LLM 标注轮次的少样本上下文。
提示词优化(受 DSPy 启发)
Potato 会运行自动化的提示词优化,为 LLM 找出最好的指令格式,并会寻找仍能守住目标准确率的最小模型:
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:整个工作流都在服务器自己的路由背后,因此进度是从面板读取的。它报告的内容是:
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。)
延伸阅读
- Solo Mode 教程:标注 10,000 个样本 -- 分步演练
- 主动学习 -- 底层的主动学习系统
- AI 支持 -- LLM 集成配置
- 质量控制 -- 标注的质量保证
- MACE -- 能力估计(对混合模式的验证有用)
有关实现详情,请参阅源文档。