Stack Overflow Blog

Part 3: Knowing when your agent doesn’t know: the confidence layer

8.5内容质量

TL;DR · AI 摘要

构建AI代理的置信度机制需融合独立信号并校准,高风险决策需第二模型验证,不确定任务必须转交人类。

核心要点

  • 置信度由模型自评、验证、历史准确率等信号加权计算(权重分别为0.25/0.35/0.15)
  • 85%的决策未经过人工判断,需通过历史准确率数据校准模型偏差
  • 置信度低于阈值的任务必须路由给人类,防止错误自动化决策

结构提纲

按章节快速跳转。

  1. 系统可信度取决于其自我认知的准确性,而非答案本身。

  2. 通过模型自评、验证、历史数据等独立信号加权计算置信度。

  3. 使用第二模型验证高风险决策,确保置信度与实际准确率匹配。

  4. 置信度低于阈值的任务必须路由给人类以防止错误。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 置信度机制
    • 独立信号
      • 模型自评
      • 验证结果
      • 历史准确率
    • 校准方法
      • 第二模型验证
      • 权重分配
    • 人类介入
      • 阈值路由
      • 错误拦截

金句 / Highlights

值得收藏与分享的关键句。

#AI代理#置信度机制#LLM系统#生产环境
打开原文

第3部分:知道何时你的代理不知道:置信度层 - Stack Overflow

2026年10月7日

第3部分:知道何时你的代理不知道:置信度层

[

代理生成的最重要的数字不是它的答案,而是它的确定程度。将这个数字由独立信号组成,检查其校准情况,用第二个模型对高风险决策进行评分,其余情况则合理地转交给人类处理。

这是运行生产级LLM系统的六级成熟度模型的第三级。前两级让你达到系统能够正常运行且可见运行状态的阶段。第三级是置信度:系统仅在经过校准的置信度较高时才自主行动,用独立的评判者对关键决策进行评分,并以实际能捕捉错误的方式将所有不确定的内容转交给人类。

这三个要素构成一个机制。置信度是决定哪些内容自动运行的调节器。评判者是构成这个调节器的信号之一——也是将决策从自动路径中触发出来的机制。人工交接是调节器将所有低于阈值的内容发送到的位置。如果这三个要素都正确实现,你将拥有一个知道自己何时不知道的系统,因此可以被赋予更多信任。

合成置信度:永远不要依赖单一来源

如果向大多数LLM系统询问"你有多确定?",你得到的往往是没有用的信息——要么没有数字,要么是模型自我报告的置信度(模型在错误时往往过分自信)。因此你必须构建一个置信度信号。生产环境中的置信度评分由独立信号组成,绝不会直接取自模型自身的声明:

code
@dataclass
class Signals:
    model_self: float            # 模型自身的评分——有用但权重最低(过度自信)
    verification: float          # 确定性检查通过的比例(0..1)
    judge_agreed: bool | None    # 如果该决策被评判则为True/False,未采样时为None(大多数情况都是None)
    historical: float            # 该切片的历史准确性(例如0.99 vs 0.80)

WEIGHTS = {"model_self": 0.25, "verification": 0.35, "judge": 0.25, "historical": 0.15}

def compose(s: Signals) -> float:
    # 仅约8%的决策会被评判。当judge_agreed为None时,移除评判项并重新归一化剩余部分——
    # 未被评判的决策不应被惩罚(False)或伪造(True)。
    parts = {"model_self": s.model_self, "verification": s.verification, "historical": s.historical}
    if s.judge_agreed is not None:
        parts["judge"] = 1.0 if s.judge_agreed else 0.0
    return sum(WEIGHTS[k] * v for k, v in parts.items()) / sum(WEIGHTS[k] for k in parts)

具体的权重分配不如原则重要:单一来源的置信度意味着单一故障点。注意model_self被故意设为最小权重——一个自信但错误的模型会因验证失败或评判者不同意而被拉低,因此"自信错误"本身无法单独通过标准。其他三个信号都是可验证的:确定性验证要么通过要么未通过,评判者要么同意要么不同意,历史准确性是关于该切片的可测量事实。模型对自己的看法是唯一无法独立验证的输入,这正是它权重最低的原因。

校准:这个数字必须有意义

一个分数只有在校准后才有意义——如果“0.9”表示“大约90%的时间是正确的”。验证方法:按预测置信度分桶,测量每个桶的实际准确率(可靠性图)。

code
预测区间  平均预测值  实际准确率  评估
----------------  --------------  ---------------  ---------------------------------------
0.90 – 1.00       0.95            0.71             ⚠ 过度自信 — 需重新校准 / 提高阈值
0.80 – 0.90       0.85            0.84             ✓ 校准良好
0.70 – 0.80       0.75            0.77             ✓
< 0.70            0.55            0.52             ✓ (正确地不确定)

如果最高置信度桶的准确率只有71%,说明自动化阈值过于宽松。生成该表格的分桶逻辑(以及需要预警的差距):

code
def reliability(samples, bins=10):           # samples: list[(confidence, was_correct: 0|1)]
    rows, ece, n = [], 0.0, len(samples)
    for b in range(bins):
        lo, hi = b/bins, (b+1)/bins
        last = (b == bins - 1)
        bucket = [s for s in samples if lo <= s[0] < hi or (last and s[0] == 1.0)]  # 最高桶包含1.0
        if not bucket: continue
        conf = sum(c for c, _ in bucket) / len(bucket)
        acc  = sum(ok for _, ok in bucket) / len(bucket)
        rows.append((round(lo, 1), round(conf, 3), round(acc, 3), len(bucket)))
        ece += (len(bucket) / n) * abs(conf - acc)
    return rows, ece

加权平均本身不具备校准特性——compose()返回的是[0,1]区间内的数值,而非概率。闭环处理:从原始组合得分到实际准确率拟合单调映射,并基于该映射进行决策。

code
from sklearn.isotonic import IsotonicRegression
calibrate = IsotonicRegression(out_of_bounds="clip").fit(raw_scores, correct)
p_correct = calibrate.predict([composed])[0]     # 这是阈值比较的实际目标

在滚动窗口上重新计算——校准会随模型和输入数据漂移。预警时优先使用Brier分数或带符号的桶间差距,而非ECE(ECE对分桶方式敏感,且对失准模型可能显示接近0)。注意独立性前提:将历史切片准确率权重融入compose()后再按切片校准会导致双重计数——应在单一位置编码切片可靠性,而非同时在两个位置。

最高形式是“弃权”——选择不作决定。一个表示“我不确定;应由人类判断”的代理比总是给出答案的代理更值得信赖。应将弃权视为首要结果,而非失败路径。这也是你抵御“未知的未知”的最佳防御:你无法列举生产环境可能发送的所有奇怪输入,但通过校准得分加上弃权阈值,这些异常情况会自动转交给人类处理。

code
ABSTAIN_FLOOR = 0.40    # 低于此值:不提出建议;升级为"需要人类处理"

整个系统围绕三个杠杆进行调整,校准数据会告诉你每个杠杆的调整方向:

code
杠杆                提高它                                    降低它
-------------------  ------------------------------------------  ---------------------------------------
阈值 `T`        减少自动错误,增加人类工作量          更多自动化,更多风险
`model_self` 权重  更信任模型(风险较高)               更依赖验证/判断
弃权阈值        减少不良自动建议,增加升级次数  减少升级次数,增加对人类的干扰

一些反模式会破坏这一层:使用原始模型置信度作为调节器(校准错误——应使用组合方式);使用单一全局阈值(不同数据切片的准确性差异巨大);从不重新校准(校准会失效——四分之一旧的阈值相当于四分之一旧的风险模型);以及将弃权视为错误(这是系统正确识别其能力边界的表现——应测量它,而非压制它)。

抽样判断者:一个对首个模型进行评分的第二模型

在 compose() 中的一个信号是 judge_agreed。这里就是它的来源——它需要单独成节,因为在高风险决策中,它决定了自信猜测与已验证答案之间的差异。

单模型通过仅是一个单一故障点。当它错误时会表现得非常自信,存在特有的盲区,而且仅凭输出无法区分正确答案与合理但错误的答案。廉价而有效的保险是引入判断者:一个独立的第二模型,在采取行动前评估首个模型的决策。

code
@dataclass
class Verdict:
    agrees: bool
    confidence: float
    failure_mode: str | None    # 如果不同意,原因是什么

def judged_decision(inputs, primary_out, judge) -> Verdict:
    return judge.evaluate(inputs=inputs, proposed=primary_out)   # 狭义问题:这个决策正确吗?

三个设计选择使其有效运作。

让判断者保持独立——且持怀疑态度。如果判断者与主模型是相同模型和相同提示词,它会共享盲区并盲目认可。尽可能使用不同模型家族、不同框架,并提示它进行反驳,而非确认。

code
你是一个严格的审查者。你将收到 INPUTS 和另一个系统提出的 PROPOSED DECISION。
你的任务是找出 PROPOSED DECISION 错误、不安全或未被输入支持的最强理由。
不要轻易同意。如果你经过真诚尝试仍无法反驳,则同意。

返回 JSON:{"agrees": bool, "confidence": 0..1, "failure_mode": string|null}

INPUTS: {{inputs}}
PROPOSED DECISION: {{proposed}}

被要求反驳的判断者能发现比被要求批准的判断者多得多的问题——这里的框架确实发挥了作用,因为一个容易同意的审查者被问“这是对的吗?”时会找到说“是”的方式,而一个对抗性审查者被问“哪里错了?”则会揭示你原本只能在生产环境中发现的失败模式。

Sample — don’t judge everything. Judging doubles model cost. Spend it where stakes or uncertainty are high:

code
decision class                          judge policy
--------------------------------------  ------------------------------------------------------
highest-impact / irreversible           100% judged
borderline confidence (near threshold)  always judged
everything else                         random sample, e.g. 5–10% (a continuous quality probe)
code
def should_judge(d, thresholds, rate=0.08) -> bool:
    if d.stakes == "high":                              return True
    if abs(d.confidence - thresholds.get(d.slice, 0.95)) < 0.05:  return True   # borderline (per-slice T, safe default)
    return deterministic_hash(d.decision_id) % 10000 < rate * 10000   # deterministic + sub-1% safe

将分歧视为路由信号——同时也是度量指标。当判断者产生分歧时,该决策必须每次都路由给人类处理。分歧率是你最重要的健康信号之一——出现峰值可能意味着攻击、部署故障或模型退化。

code
def combine(primary_out, verdict: Verdict) -> tuple:
    emit("judge_invocations_total")
    if not verdict.agrees and verdict.confidence >= 0.6:    # confident refutation → human, every time
        emit("judge_disagreements_total")
        return route_to_human(primary_out, reason=verdict.failure_mode), False   # judge_agreed = False
    if not verdict.agrees:                                  # weak/low-confidence refutation: INCONCLUSIVE
        return primary_out, None        # don't fake agreement — None makes compose() drop the judge term
    return primary_out, True            # genuine agreement → judge_agreed = True for compose()

一个容易忽视的陷阱是测试中的非确定性。如果判断者采用随机采样,而该路径在测试套件中运行,你的测试将变得不可靠——相同输入在一次运行中被判断,下一次却可能不被判断,导致断言间歇性失败。这看起来像是一个神秘的bug,实际上是采样率泄露到了确定性测试中。在测试中固定采样率,并使用决策ID的确定性哈希(而非随机数生成器)以确保即使采样行为也能复现:

code
# production samples; tests pin the rate. Any randomness in an agent must be injectable.
judge = Judge(model=secondary, sampling_rate=0.0 if TESTING else 0.08)

你并不是在追求模型完美,而是在构建一个比任何单次调用都更可靠的系统——对于被判断的决策,必须有两个独立模型达成一致后才能采取行动,它们的分歧就是系统发出的警报。对于关键决策,这是一种以极低成本换取大量安全性的惊人方式。

交接:人机交互用户体验

判断机制和人工审核最终都会导向同一个地方:将决策路由给人类处理。但“路由给人类”正是许多优秀系统悄然失败的地方——问题不在模型本身,而在交接环节。如果设计得当,人类将成为真正的安全层和训练信号来源;如果设计得差,你可能只是制造了一个人们点击而不阅读的“批准”按钮——这比没有人类参与更糟糕,因为它制造了虚假的责任感。

懒惰的交接方式只会展示提议答案并提供批准/拒绝选项。在高负载情况下,人类倾向于批准——提案会引导他们,拒绝需要付出努力,而队列很长。现在你拥有了一个不检查任何内容的人类参与者,以及一条虚假声称“有人审查过”的审计轨迹。优秀的 HITL(人机协作)用户体验应让真实判断变得廉价,并将注意力引导到真正重要的地方。

展示草案时应附带证据而非直接给出结论。对抗形式化批准最有效的手段是展示推理过程和证据——人类无法检查他们看不到依据的结论。

code
{
  "proposed": { "decision": "approve", "fields": { "amount": 250 } },   // 可编辑的草案,而非最终结论
  "confidence": 0.62,
  "routed_because": "confidence_below_threshold (0.62 < 0.85)",          // 指明需要关注的位置
  "reasoning": ["matched policy 4.2", "no prior flags"],                 // 说明原因而非仅陈述结果
  "evidence": [ { "label": "policy", "ref": "...", "snippet": "..." } ],
  "stakes": "reversible | high_cost | irreversible",                     // 调整其关注度
  "alternatives": [ { "decision": "reject", "would_trigger": "..." } ]
}

将每个操作都视为信号。每次人工操作都是一个标签——按决策结构化记录。编辑是最有价值的信号:修正后的答案既是黄金数据集的样本,也能揭示代理系统性错误的位置。

code
def on_review(decision_id, review):
    ledger.append_outcome(decision_id, review)            # 审计:谁决定的,什么,何时
    emit("human_override_rate", 1.0 if review["action"] != "approve" else 0.0,
         slice=slice_of(decision_id))
    if review["action"] == "edit":
        golden_set.add_candidate(decision_id, review["corrected"])   # 编辑内容成为评估案例

将反馈闭环传递回系统。HITL(人机协同)与置信度阈值构成一个反馈环,这正是使整个系统具备自优化能力而非静态的关键:

code
观察结果        操作
------------------  -------------------
人类几乎一致批准   可自动化候选 — 降低该切片的T值
频繁覆盖         提高T值或将该切片回退至纯人工处理
对同一字段的多次编辑  代理在该领域系统性错误 — 修正提示/逻辑

你可以有理有据地开放自动化——指向每个区间对应的批准率作为依据。这是一个自我闭环的系统:法官的分歧和人类的编辑都会反馈到历史记录和切片准确率信号中,这些信号被compose()读取,因此每次路由决策都会让下一批自动化决策更精准。

不要让人类陷入过载。将所有决策都路由给人类并不是安全措施;这会成为对评审人员的拒绝服务攻击,导致队列被淹没后反而出现形式化批准。良好置信度评估和路由机制的意义在于:只有真正存在不确定性的决策才会到达人类——数量足够少,才能获得真正的关注。如果队列过于庞大,请从源头解决(提升置信度、自动化已验证安全的切片),而不是增加评审人员。另外两个需要特别注意的陷阱:将拒绝操作设计得比批准更繁琐(单击操作与五字段表单的对比会偏向批准),以及遗漏routed_because字段(人类不知道该查看何处,因此会草率处理)。

核心要点

置信度是安全自动化的关键因素,它是三部分机制中的一部分。将数字由独立信号组成——而非模型自身的断言——并将其与实际结果进行校准,使“0.9”真正代表90%。对于重要的决策,应由独立且持怀疑态度的评审员尝试证明第一个模型的错误,根据风险等级进行抽样,并保持确定性以确保测试的合理性;评审员的一致意见会提升评分,而意见分歧则会触发警报。所有低于阈值的情况都将转交给人类处理,同时附上草案、推理过程以及路由原因——反对意见与批准意见同样便捷,每一次操作都会作为信号,用于调整阈值的上下限。

采用这种方式构建的系统能够识别自身知识的局限性。它值得被赋予更多信任,因为那些不应处理的案例会自动被排除——而看似存在监管的假象(这是最危险的结果)正是它坚决避免制造的。

系列文章:在生产环境中运行大型语言模型系统 —— 第6级共3级:置信度

这些文章采用知识共享署名-相同方式共享4.0国际许可协议进行授权。

creativecommons.org/licenses/by-sa/4.0/deed.en