返回机制实验室

W11 · S71—S77 · 总 Day 161167

人机协作:有人复核,也可能无人及时处理

用确定性队列观察复核人数、等待和反馈标签的边界。

作者准备的学习示例 · 不计真实学习进度 · 不代表生产 / GPU / 真机结果

在仓库根目录运行;只输出合成示例,不写文件、不访问网络

npm run learning:p2 -- w11
跳到完整源码 ↓

核心问题

“所有结果都交给人工”听起来很稳妥,但如果任务每两分钟来一个,每个复核要五分钟,一个人就无法跟上。积压、匆忙点击与错过时机,会改变这个流程实际提供的保护。

对应 S71~S77。把人的参与看成有容量、有上下文、有判断权的工作系统,而不是一个布尔开关。

1. 先把时间算清楚

本例八个案例在 0、2、4……14 分钟到达,每个需要 5 分钟复核。FCFS 按到达先后分配给最早空闲的人。它假设人人速度相同、没有休息、没有切换损耗,也没有优先级。

到达率为 0.5 个/分钟,单人服务能力为 0.2 个/分钟。持续相同输入时,一个人的负载比为 2.5,长期积压会增长。三人的名义能力为 0.6 个/分钟,但“总容量略大于到达率”在有波动、疲劳与复杂案件时仍不保证低等待。

npm run learning:p2 -- w11

默认单人时,第八个案例在第 14 分钟到达、第 35 分钟才开始,等待 21 分钟。三人时,在这组完全规则的输入下最大等待为 0。零等待来自模拟条件,不能写成增加两人就一定解决真实队列问题。

2. 复核结果不天然是金标准

人的判断可能受模型建议锚定、证据呈现、时间压力和权限限制影响。审核员点了同意,可能是认同,也可能是缺少信息、无法修改或急于清空队列。记录反馈时应保留决策人、当时可见证据、模型版本、处理时间和理由来源。

解释界面可以选择先显示证据,再显示建议;也可以允许暂缓、升级、修改、撤回或说明不确定。但哪一种顺序更好,需要合适的用户研究与场景证据,当前排队函数没有测量认知效果。

问题仅统计通过率会漏掉什么
是否能及时处理等待时长、积压年龄、错过业务窗口
是否有真实判断权能否拒绝、修改、升级与纠正
是否有充分信息证据缺失、模型建议造成的锚定
反馈能否回流学习来源不明、选择偏差、标签延迟

3. 轻量修改

把其中一个案例 reviewMinutes 改成 20,其余保持不变,比较一个人和三个人的队列。这会引入工作量差异,但仍没有随机性。重点是找到哪个后续案例被拖延,而不只比较最大值。

另一个可选思考:允许高风险案例插队可能减少严重事件等待,却让普通案例饥饿。怎样显示等待年龄、保留人工判断并避免永远被插队?写一个原则即可,不开发完整排班系统。

4. 专业材料与仓库连接

  • Google PAIR Guidebook:作为人机反馈与控制的阅读入口,结合本例追问“使用者有没有有效控制权”;不要把队列模拟当成用户研究结论。
  • FSDL 2022:选 Data Annotation,思考标注流程、任务定义与反馈质量如何影响训练数据。

可选阅读仓库 src/aml/hitl.ts,比较其中的人机工作流与本例等待模型。先识别状态和责任,不要求把示例合并进现有产品。

5. 向 AGI 与具身场景延伸

人类反馈可能参与偏好学习、纠错与长任务协作,但反馈有延迟、成本和偏差。具身任务的接管还有更紧迫的反应时间、控制权切换和环境状态问题。这里的分钟级人工队列不是安全接管机制,不应据此配置真机。

本周笔记可只写:谁有权改变结果;最长可能等多久;我得到的反馈为什么不一定是真值。

配套日课:按需要补充理论

本实验贯穿一周,不要求一天做完。

可运行源码

src/learning/ai-systems/w11-human-review.ts · 构建时直接读取源文件,避免讲义代码与实现各自漂移。

interface Case { id: string; arrivedAt: number; reviewMinutes: number }
export const cases: Case[] = Array.from({ length: 8 }, (_, i) => ({ id: `case-${i + 1}`, arrivedAt: i * 2, reviewMinutes: 5 }))

export function reviewQueue(input: Case[], reviewers: number) {
  if (!Number.isInteger(reviewers) || reviewers < 1) throw new Error('At least one reviewer')
  const freeAt = Array<number>(reviewers).fill(0)
  const rows = [...input].sort((a, b) => a.arrivedAt - b.arrivedAt).map(item => {
    const worker = freeAt.indexOf(Math.min(...freeAt))
    const start = Math.max(item.arrivedAt, freeAt[worker])
    freeAt[worker] = start + item.reviewMinutes
    return { ...item, reviewer: worker + 1, start, finish: freeAt[worker], waitMinutes: start - item.arrivedAt }
  })
  return { reviewers, rows, maxWaitMinutes: rows.length ? Math.max(...rows.map(r => r.waitMinutes)) : 0 }
}

export function run() {
  return {
    oneReviewer: reviewQueue(cases, 1), threeReviewers: reviewQueue(cases, 3),
    offeredCasesPerMinute: 0.5, singleReviewerCapacityPerMinute: 0.2,
    boundary: 'Deterministic FCFS simulation; no measured human accuracy, fatigue, priority, or automation bias.',
  }
}