Skip to content

AI 支持

接入 OpenAI、Anthropic、Gemini、Ollama、vLLM 或 HuggingFace,让标注者按需获得提示、关键词高亮和标签理由。

开启 AI 支持后,标注者工作时会有一个大语言模型写提示、高亮关键词、解释每个标签为什么可能适用。它不会替你填表单。决定权仍在标注者手里,每个助手都藏在一个需要他们按下的按钮后面。

安装

各家提供商的 SDK 都是可选依赖。基础安装不需要其中任何一个,只有当配置选中某个 endpoint_type 时才会导入对应的 SDK:

bash
pip install "potato-annotation[ai]"

这覆盖了常用的那几个。如果配置选了一个 SDK 缺失的端点,服务器会告诉你该装哪个包,而不是在导入时就失败。

支持的提供商

云端: openai、anthropic、gemini、huggingface

本地: ollama、vllm

视觉: ollama_vision、openai_vision、anthropic_vision、yolo

视觉端点以及图像和视频助手请见视觉 AI 支持。

配置

yaml
ai_support:
  enabled: true
  endpoint_type: "openai"
  ai_config:
    model: "gpt-4o-mini"
    api_key: "${OPENAI_API_KEY}"
    temperature: 0.7
    max_tokens: 800
    include:
      all: true

关于这个块有两点值得直说,因为运行时哪一点都不会被发现:

模型设置放在 ai_config 里面,比想当然的位置深一层。ai_support.model 和 ai_support.base_url 会被忽略,不过 potato validate --strict 会把它们指出来。

include.all 默认是关闭的。没有它,页面上任何地方都不会出现助手按钮,AI 面板渲染成空的,校验干净通过,也没有任何警告。

配置选项

选项类型描述
enabledboolean开启 AI 支持
endpoint_typestring使用哪个提供商
ai_config_filestring存放端点细节的外部配置文件路径
ai_config.modelstring模型名称,默认为该提供商自己的默认模型
ai_config.api_keystring云服务商必填
ai_config.base_urlstring用某个兼容 OpenAI 的服务器替代厂商自家的,例如 vLLM、SGLang、LM Studio 或 LiteLLM。设置它之后 api_key 变为可选
ai_config.temperaturefloat回复的随机程度,0.0 到 2.0,默认 0.7
ai_config.max_tokensinteger回复的最大长度,默认 800
ai_config.include.allboolean在所有方案上启用助手,默认 false
ai_config.include.special_includeobject按页面、按方案分别控制
cache_config.disk_cache.enabledboolean把回复缓存到磁盘,默认 false
cache_config.disk_cache.pathstring缓存文件路径,开启缓存时必填
cache_config.prefetch.warm_up_page_countinteger启动时预先生成的实例数
cache_config.prefetch.on_nextinteger向前翻页时预取的实例数
cache_config.prefetch.on_previnteger向后翻页时预取的实例数

openai 和 openai_vision 接受三种提供 API 密钥的方式,校验三种都认:api_key 的值、环境中的 OPENAI_API_KEY,或指向一台不需要密钥的自托管服务器的 base_url。anthropic、huggingface 和 gemini 只读第一种,所以对这三个来说密钥确实是必需的。

没有理由就让 max_tokens 保持默认值。理由助手和关键词助手会在一次回复里为所有标签作答,所以几百个 token 会把回复从对象中间截断,助手渲染成空的,而且不报错。

几个助手

标注者在每个方案上会拿到三个按钮,外加第四种随提示一起出现的行为。

提示给出关于如何思考这条数据的指引,可以附带一个建议标签。

关键词标出与每个标签相关的词和短语,画成文本上的方框覆盖层,而不是背景高亮。

理由解释每个可选标签为什么可能适用,对所有标签给出平衡的推理。在培训标注者或判断确实困难时,这个最值得开启。

标签建议不是单独的按钮。当提示带有建议标签时,那个标签会带上琥珀色边框和一个闪光标记。

各端点能做什么

端点文本视觉方框关键词理由
openai、anthropic、ollama是否否是是
openai_vision、anthropic_vision、ollama_vision是是否仅文本条目是
yolo否是是否否

「仅文本条目」是按条目判定的,而不是按端点判定。视觉模型仍然是语言模型,所以在文本条目上它提供全部三个助手,而在图像条目上关键词按钮会被过滤掉,因为没有词可标。

图文都有的条目

带截图的客服工单,或配着评论的产品照片,应该图文一起送到模型。Potato 从 item_properties.text_key 取文本,图片则取下面第一个能解析出结果的来源:

  1. ai_support.image_key
  2. item_properties.image_key
  3. instance_display 中 type: image 的字段,或第一个取值看起来像图片的展示字段

所以已经通过 instance_display 声明了照片的配置,不需要再写一遍:

yaml
instance_display:
  fields:
    - {key: body, type: text, label: Ticket}
    - {key: screenshot, type: image, label: Screenshot}

只把图片发过去,提示就会信心十足地描述这条数据里错误的那一半。给模型一张通用配图,再让它处理「导出按钮一直转圈」,它会回说自己没有拿到上下文。

这个字段接受 http(s) URL、data URI,或 media_directory 下的文件名。本地文件会从磁盘读出并以 base64 发送。既不是 URL、Potato 也找不到对应文件的值会被记进日志,然后不带图片去问模型,所以一条只针对文本推理的提示,就是去查那行日志的理由。

按方案启用助手

include.all 在所有地方开启所有助手。另一种做法是 special_include,它把页面索引映射到标注 id,再映射到该方案应带的助手列表:

yaml
ai_support:
  enabled: true
  endpoint_type: "openai"
  ai_config:
    model: "gpt-4o-mini"
    api_key: "${OPENAI_API_KEY}"
    include:
      all: false
      special_include:
        "0":
          "0": ["hint", "keyword"]
        "1":
          "1": ["hint"]
        "2":
          "0": ["hint", "keyword"]
          "1": ["hint", "keyword"]

内层的键是标注 id,也就是该方案在 annotation_schemes 中从零开始数的位置。

当表单按网格排布时,这一点比看上去更要紧。助手那一行是按方案渲染的,所以三个方案各三个助手就是九个按钮,每个单元格一行。单元格宽度低于约 260px 时,这一行会折叠成单行图标,标签保留为按钮的无障碍名称并放在它的 title 上。如果要把 layout.grid 和 AI 支持一起用,请按方案指定想要的助手,而不是在所有地方全部打开。

缓存与预取

一次大语言模型调用会给页面加载添上好几秒,而等待的标注者就不再按那个按钮了。缓存还能让你不必为别人重新访问过的条目付两次钱:

yaml
ai_support:
  enabled: true
  endpoint_type: "openai"
  ai_config:
    model: "gpt-4o-mini"
    api_key: "${OPENAI_API_KEY}"
    include:
      all: true
  cache_config:
    disk_cache:
      enabled: true
      path: "annotation_output/ai_cache.json"
    prefetch:
      warm_up_page_count: 20
      on_next: 10
      on_prev: 3

预热在启动时为前 N 条实例预先生成回复。预取则在标注者浏览时于光标前方生成,on_next 比 on_prev 大,反映的是大部分移动都是向前的。磁盘缓存在重启后依然保留。

外部 AI 配置文件

端点细节往往正是不能提交进仓库的那部分:API 密钥、内部服务器 URL、那台机器上装的是哪个模型。ai_config_file 把它们移进自己的文件:

yaml
# config.yaml, committed, no secrets
ai_support:
  enabled: true
  ai_config_file: ai-config.yaml
 
  ai_config:
    temperature: 0.7
    max_tokens: 800
    include:
      all: true
yaml
# ai-config.yaml, gitignored
endpoint_type: ollama
model: qwen3:0.6b
base_url: http://localhost:11434

外部文件负责设置 endpoint_type,它的其他值会合并进 ai_config 并优先于内联的值。合并是扁平的:外部文件在自己的顶层直接放 model、base_url 和 api_key。把它们包在该文件里的 ai_config: 块中会产生 ai_config.ai_config,没有任何代码读它,启动随后会因缺少 API 密钥而失败,报错指向的还是错的键。

如果设置了 ai_config_file 但文件不存在,AI 支持会给出警告并自行关闭,服务器照常启动。potato validate --strict 把那条警告当成错误,所以一项以 AI 辅助为研究对象的研究,在 AI 关着的情况下过不了自己的检查。请在配置旁边附一份 ai-config.yaml.example 供人复制。ai-config.yaml 和 ai-config*.yaml 这两个模式已被 gitignore。

自定义提示词

提示词以 JSON 文件的形式放在 potato/ai/prompt/ 里,每种标注类型一个:

标注类型提示词文件
Radioradio.json
Likertlikert.json
Multiselectmultiselect.json
Spanspan.json
Sliderslider.json
Selectselect.json
Numbernumber.json
Free texttextbox.json

每个文件为每个助手保存一个模板,包含 name、prompt、output_format 和一个图标:

json
{
  "hint": {
    "name": "Hint",
    "prompt": "TASK: Generate annotation guidance...\n- Text to annotate: \"${text}\"\n- Annotation task: ${description}\n- Available labels: ${labels}",
    "output_format": "default_hint",
    "img": "/static/ai_assistant_img/blub.svg"
  }
}

可用的变量是 ${text}、${description}、${labels}、${min_label}、${max_label}、${size}、${min_value} 和 ${max_value}。

output_format 指明渲染器期待的回复结构。default_hint 返回 {hint, suggestive_choice},default_keyword 返回 {label_keywords: [{label, keywords}]},default_rationale 返回 {rationales: [{label, reasoning}]}。

各提供商的配置

OpenAI

yaml
ai_support:
  enabled: true
  endpoint_type: "openai"
  ai_config:
    model: "gpt-4o-mini"
    api_key: "${OPENAI_API_KEY}"
    temperature: 0.7
    max_tokens: 800
    include:
      all: true

Anthropic

yaml
ai_support:
  enabled: true
  endpoint_type: "anthropic"
  ai_config:
    model: "claude-sonnet-4-20250514"
    api_key: "${ANTHROPIC_API_KEY}"
    temperature: 0.7
    max_tokens: 800
    include:
      all: true

Google Gemini

yaml
ai_support:
  enabled: true
  endpoint_type: "gemini"
  ai_config:
    model: "gemini-2.0-flash-exp"
    api_key: "${GOOGLE_API_KEY}"
    max_tokens: 800
    include:
      all: true

Ollama

yaml
ai_support:
  enabled: true
  endpoint_type: "ollama"
  ai_config:
    model: "llama3.2"
    base_url: "http://localhost:11434"
    max_tokens: 800
    include:
      all: true

vLLM

yaml
ai_support:
  enabled: true
  endpoint_type: "vllm"
  ai_config:
    model: "meta-llama/Llama-3.2-3B-Instruct"
    base_url: "http://localhost:8000"
    max_tokens: 800
    include:
      all: true

HuggingFace

yaml
ai_support:
  enabled: true
  endpoint_type: "huggingface"
  ai_config:
    model: "meta-llama/Llama-3.2-3B-Instruct"
    api_key: "${HF_API_KEY}"
    max_tokens: 800
    include:
      all: true

OpenRouter 不在 ai_support.endpoint_type 接受的十个取值之内,在这里写它,配置加载不起来。它能用在聊天侧栏:chat_support.endpoint_type: openrouter,注意这个拼写没有下划线。

给图像单独配一个端点

文本模型和视觉模型可以并排运行,让每种内容类型都用上更合适的那个:

yaml
ai_support:
  enabled: true
  endpoint_type: "ollama"
  visual_endpoint_type: "ollama_vision"
  ai_config:
    model: "llama3.2"
    include:
      all: true
  visual_ai_config:
    model: "qwen2.5-vl:7b"

完整示例

yaml
annotation_task_name: "Sentiment Analysis with AI Support"
task_dir: "."
 
data_files:
  - "data/reviews.json"
 
item_properties:
  id_key: id
  text_key: text
 
annotation_schemes:
  - annotation_type: radio
    name: sentiment
    description: "What is the sentiment of this text?"
    labels: ["positive", "negative", "neutral"]
 
  - annotation_type: multiselect
    name: topics
    description: "What topics are discussed?"
    labels: ["product", "service", "price", "quality"]
 
ai_support:
  enabled: true
  endpoint_type: "openai"
  ai_config:
    model: "gpt-4o-mini"
    api_key: "${OPENAI_API_KEY}"
    temperature: 0.7
    max_tokens: 800
    include:
      all: true
  cache_config:
    disk_cache:
      enabled: true
      path: "annotation_output/ai_cache.json"
    prefetch:
      warm_up_page_count: 20
      on_next: 10
      on_prev: 3
 
output_annotation_dir: "output/"
export_annotation_format: jsonl
 
user_config:
  allow_all_users: true

环境变量

把密钥留在配置之外:

bash
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export GOOGLE_API_KEY="..."

用 ${VARIABLE_NAME} 引用它们,主配置和外部 AI 配置文件里都能用。

故障排查

提示框是空的,但服务器确实回复了

模型回复了,请求返回 200,而提示框什么都不显示。有四件事会产生同样的症状,按这个顺序检查:

  1. include.all 缺失。 页面上根本没有助手按钮,而且没有任何警告。
  2. max_tokens 太低。 理由助手和关键词助手会一次性为所有标签作答,所以回复从对象中间被截断。日志里会写模型触到了 max_tokens。
  3. 服务器忽略了 schema。 检查回复是 JSON 而不是散文。没有约束解码的服务器会按提示词暗示的任何结构作答。
  4. 键放错了地方。 所有跟模型有关的东西都属于 ai_config。validate --strict 会指出它不认识的任何键。

一个按钮都没有,而启动日志看着正常

在日志里找 "AI endpoint ready"。如果它说端点启动失败,原因就在那一行上方的警告里。

Ollama 或 vLLM 连接失败

确认服务器正在运行、模型已经拉取,Ollama 用 ollama list 查看,并检查配置的 base_url 可以访问。

最佳实践

  1. 当成辅助来用 - 决定由标注者做出,一项默认接受建议的研究测的是模型
  2. 在生产环境启用缓存 - 它能降低延迟和重复开销
  3. 使用预热 - 预先生成开头那几条实例,第一位标注者就不用等
  4. 留意账单 - 云服务商按请求收费,每个方案都配一个助手会在整个语料上成倍放大
  5. 考虑本地端点 - 大批量工作,或语料不能离开本机时,用 Ollama 或 vLLM
  6. 把密钥放在环境变量里 - 绝不要提交它们

延伸阅读

有关实现细节,请参阅源文档。