错误传播与补偿机制
LLM 调用的失败方式
传统 API 调用只有两种结果:成功或失败,返回预期结果或抛出异常。LLM 调用至少有四种不同的失败方式:
硬失败: API 超时、速率限制、服务不可用。这与传统 API 失败相同,可以用标准的重试和熔断机制处理。
格式失败: LLM 返回了内容,但不符合预期的结构。JSON 解析失败、字段缺失、类型不匹配。第四章讨论的 Pydantic 验证器可以捕获这类错误。
语义偏离: LLM 返回了格式正确的内容,但语义上偏离了预期。回答了错误的问题、忽略了关键约束、生成了不相关的内容。这类错误最难检测,因为它通过了所有结构验证。
幻觉: LLM 返回了格式正确、看起来合理,但事实上不正确的内容。引用了不存在的文献、编造了数据、给出了错误的结论。幻觉是语义偏离的特殊形式,格外危险——因为它往往表现得很有把握。
这四种失败方式需要不同的检测和处理策略。多数 LLM 应用的错误处理不到位,根源就在于把这四种失败混为一谈。
多步工作流中的错误传播
在多步工作流中,一个步骤的错误输出会成为下一个步骤的错误输入。这种错误传播的后果通常比原始错误更严重——因为下游步骤可能基于错误的输入做出进一步的错误推理,让错误不断放大。
步骤A(正确输入) → 语义偏离的输出 → 步骤B(错误输入) → 进一步偏离 → 步骤C → 完全错误的最终结果这与第一章讨论的级联可靠性问题是同一个结构。如果每个步骤有 90% 的概率产生正确输出,三步串联后的整体正确率是 0.9^3 = 72.9%。五步串联后是 59%。步骤越多,整体可靠性下降越快。
工程上有两个应对思路:减少步骤数量(隐式编排优于显式编排的又一个理由),或在步骤之间插入验证点。
验证点设计
验证点是工作流中显式的质量检查站。在每个验证点,系统检查当前步骤的输出是否符合预设条件,再决定是继续、重试还是中止。
from pydantic import BaseModel, Field
from typing import Literal
class StepResult(BaseModel):
output: dict
confidence: float = Field(ge=0, le=1)
class ValidationDecision(BaseModel):
action: Literal["continue", "retry", "abort"] = Field(
description="验证结果:继续执行、重试当前步骤、或中止工作流"
)
reason: str = Field(description="决策理由")
def execute_with_validation(steps: list, input_data: dict) -> dict:
current = input_data
for step in steps:
for attempt in range(3):
result = step.execute(current)
if step.validate(result):
current = result
break
else:
# 三次重试均未通过验证
return {"status": "aborted", "failed_step": step.name}
return {"status": "completed", "result": current}验证逻辑应该是确定性的——用代码检查结构约束和业务规则。用 LLM 判断输出"是否合理"只会用不确定性去验证不确定性,不会提高整体可靠性。
补偿策略
当错误不可避免时,系统需要补偿策略来恢复到一致状态。
重试(Retry)。 最简单的补偿:用相同的输入重新执行失败的步骤。适合硬失败和格式失败。对于语义偏离,盲目重试可能返回相同的错误结果——因为同一个 prompt 同一个上下文里容易产生相似的输出。
携带反馈的重试。 将错误信息附加到 prompt 中,明确告诉 LLM 上次输出的问题。比盲目重试更有效,因为 LLM 获得了关于失败原因的额外信息。
降级(Degradation)。 用更简单但更可靠的方法替代失败的步骤。如果 LLM 的意图分类失败,回退到基于关键词的规则匹配;如果复杂推理失败,回退到模板化的回复。降级牺牲了质量以保证可用性。
回退到检查点(Checkpoint Rollback)。 在工作流的关键节点保存中间状态。当后续步骤失败时,可以回退到最近的检查点,从一个已知正确的状态重新执行。适合步骤多、执行成本高的工作流。
选哪种补偿策略,看两点:失败的可恢复性和业务的容错度。格式失败可以通过重试恢复;幻觉通常无法通过重试恢复(需要外部验证)。关键业务要求高可靠性(优先选择降级以保证可用性);低优先级任务可以容忍重试带来的延迟。
错误处理的反模式
忽略语义偏离。 只检查格式、不检查语义。输出通过了 JSON Schema 验证就认为正确。这是虚假的安全感。
无限重试。 没有最大重试次数限制,或者限制设置过高。LLM 的语义偏离不会因为多次重试而自动修正——如果第三次重试仍然失败,第十次大概率也会失败。
用 LLM 验证 LLM。 用另一个 LLM 调用来验证第一个 LLM 的输出。这在某些场景下有价值(如用小模型快速筛查明显错误),但作为通用策略是有问题的——在原有的不确定性上又加了一层不确定性。
静默降级。 降级发生了但没有通知用户。用户以为得到了高质量的 LLM 分析结果,实际上得到的是基于规则的模板回复。用户应该知道自己拿到的是什么。