产线上跑 Agent:LLM 挂了不是 500 错误,是停线

接上一篇。工具注册中心让 Agent 能操控 AOI 设备了。下一个问题:每次工具调用背后都有 LLM 调用,而 LLM API 的可靠性跟工业相机差了两个数量级。


工业设备有个基本假设:你发指令,它执行,响应时间可预测。AOI 相机的采集延迟是毫秒级确定的,运动控制卡的响应周期是 0.16 秒------不快,但稳定。

LLM API 不是这样的。它可能会:

  • 在 50ms 内返回(运气好)
  • 在 30 秒后超时(网络抖动)
  • 返回 429 告诉你"等一下"(隔壁产线也在用同一个 API key)
  • 返回 200 但内容是乱码(模型幻觉,极少但存在)
  • 完全不响应(服务端挂了)

一个标准的 Agent 循环里有多次 LLM 调用:分析缺陷图片要调用一次,决定用哪个工具要调用一次,改写 XML 参数前要调用一次确认。任何一次失败都可能打断整个闭环。

在产线场景下,"重试"不是一个可以随便做的决定------如果 Agent 正在等待 AOI 检测结果来判决当前批次是否放行,你重试 3 次各等 30 秒,整条线就多等了 90 秒。在 0.16 秒节拍的世界里,这是不能接受的。


一、不同错误,不同命运

容错的第一件事不是"怎么重试",是判断要不要重试

错误 你的反应
429 限流 等 API 告诉你等多久,精确等到那个时间再试
500/502/503 指数退避重试,同时切到备用模型
超时 递增超时重试(第一次 30s,第二次 45s)
401/403 别重试。 API Key 有问题,重试一万次也没用
Context 超限 截断历史消息后重试,而不是增加超时

分类器的核心逻辑不是魔法------关键词匹配 + HTTP 状态码:

python 复制代码
class LLMErrorClassifier:
    STATUS_MAP = {429: RATE_LIMIT, 502: SERVER_ERROR, 401: AUTH, ...}

    KEYWORD_RULES = [
        (["限流", "rate limit", "too many requests"], RATE_LIMIT),
        (["超时", "timeout", "ConnectTimeout"], TIMEOUT),
        (["认证", "invalid_api_key"], AUTH),
        ...
    ]

但有一个细节值得说:从 429 响应中提取 Retry-After header。API 明确告诉你等 15 秒,你就精确等 15 秒------而不是用通用退避公式算一个 2 → 4 → 8 秒的序列。精确等待在产线场景下意味着恢复时间可控。


二、重试不是"等一下再试",是递增超时 + 抖动

固定等待 2 秒重试 3 次的问题:

  • 如果超时是因为模型慢(大 prompt + function calling),你每次给同样的 timeout,每次都超时
  • 如果多个 Agent 同时被限流后同时重试,它们会再次同时被限流(惊群效应)

所以重试策略要带递增超时随机抖动

python 复制代码
for attempt in range(1, max_attempts + 1):
    # 每次重试给更长的超时
    current_timeout = min(base_timeout * (1.5 ** (attempt - 1)), max_timeout)

    try:
        response = client.create(model=model, messages=messages, timeout=current_timeout)
        return response
    except Exception as exc:
        err_type, retry_after = classify(exc)
        if not err_type.retryable:
            break  # 认证/参数错误不重试

        delay = retry_after or (base_delay * (2.0 ** (attempt - 1)))
        delay = min(delay, max_delay)
        delay *= (0.5 + random.random() * 0.5)  # 抖动
        time.sleep(delay)

jitter 不是算法优化,是分布式系统的生存策略------没有它,定时器会让所有重试在同一毫秒触发。


三、四级降级:从主模型到优雅降级

重试 3 次全失败后,传统做法是抛异常。但在 Agent 场景下,用户(操作员)还在等检测结果------你不能返回一个 Python traceback。

降级链是四层递进:

arduino 复制代码
DeepSeek-Chat(主模型)
  ↓ 连续失败
DeepSeek-Reasoner(备用模型,推理更强但更慢)
  ↓ 也失败
LLM 响应缓存(近期成功调用的语义匹配)
  ↓ 未命中
"检测系统暂时异常,请稍后重试或切换人工复判模式"

缓存降级在这一层特别有用。 AOI 产线的 Agent 对话有很强的重复性------"分析这张图的缺陷类型"、"检查当前 canny 阈值是否需要调整"、"输出这批的 NG 统计"。这些 prompt 的语义高度相似,LRU 缓存(100 条 / TTL 5 分钟)的命中率在实际使用中比想象的高得多。API 全挂时,缓存仍然可以返回上一次相似分析的结果结构。

缓存 key 的生成方式直接但不粗暴:

python 复制代码
key = hashlib.md5(json.dumps(messages, sort_keys=True) + "|" + model).hexdigest()

在 Agent 循环中,消息格式高度结构化(system prompt + user message + tool result),hash 冲突不是实际风险。


四、统一的结果封装:让 Agent 知道发生了什么

每一次 LLM 调用,无论经历了什么路径(直连成功 / 重试后成功 / 切备用模型成功 / 缓存命中 / 优雅降级),都返回同一个结构:

python 复制代码
@dataclass
class LLMResult:
    content: str          # 生成的文本
    model: str            # 实际用的模型(可能是备用的)
    latency_ms: float     # 总耗时(含重试等待)
    success: bool
    attempts: int         # 经历几次调用才拿到结果
    is_fallback: bool     # 是不是降级结果
    fallback_level: int   # 0=主模型, 1=备用, 2=缓存, 3=兜底
    recovery_hint: str    # 给 Agent 的恢复建议:"限流中,建议降低调用频率"

Agent 的上层逻辑不需要关心重试了几次------只看 successcontent。但 recovery_hint 会告诉它为什么 失败以及怎么恢复,这对自愈型 Agent 至关重要。

比如 recovery_hint 说"当前模型触发限流",Agent 可以决定:暂时跳过非关键的工具调用,只保留 AOI 检测和 XML 读取这两个核心工具。


五、怎么接入 LangGraph:透明替换

LLMGuard 的 API 是原始的 OpenAI 消息格式([{"role": "user", "content": "..."}])。为了让它在 LangGraph 的 create_react_agent 中无缝替换 ChatOpenAI,需要一个轻量的 LangChain wrapper:

python 复制代码
class GuardedChatModel(BaseChatModel):
    guard: LLMGuard

    def _generate(self, messages, **kwargs):
        # LangChain messages → OpenAI dicts
        # LangChain tools → OpenAI function calling format
        result = self.guard.chat(messages=openai_messages, tools=tools, ...)

        # OpenAI response → LangChain AIMessage (含 tool_calls)
        ...

对 Agent 循环完全透明------它只知道自己在调一个 ChatModel,不知道下面重试了 2 次还切了一次备用模型。


六、跟互联网 Agent 场景的区别

互联网场景(客服、搜索、文档问答)下,LLM 调用是"越多越快越好"。产线场景下,需求不一样:

  • 不是越快越好,是可预测。 你知道一次调用最坏要多久(四级降级的总超时是可计算的),才能决定 Agent 能不能在节拍内完成决策。
  • 不是越多越好,是够用就行。 缓存命中率高不是因为技术好,是因为产线场景的 prompt 确实比 C 端对话规律得多。
  • 失败不只是 UX 问题。 互联网 Agent 挂了用户换个姿势重问。产线 Agent 在检测 → 调参闭环中挂了,需要人工介入------容错层的最坏情况设计比最好情况优化重要得多。

LLMGuard 本身只有 ~1000 行,核心逻辑不复杂。真正的工程难点不在代码量,而在想清楚每次失败后系统应该处于什么状态------这需要你真的在产线上跑过,才知道哪些错误类型最常见、降级到什么程度还能接受。

下一篇可能会写最核心的模块:AOI XML 配置改写------也就是 Agent 怎么读懂检测参数、判断调整方向、安全地写入上位机配置。这是整个项目里跟"通用 Agent"最无关、跟我的日常工作关系最紧密的部分。

代码在 github.com/JamesW-ang/...

相关推荐
生成论实验室2 小时前
《事件关系阴阳博弈动力学:识势应势之道》第四篇:降U动力学——认知确定度的自驱演化
人工智能·科技·神经网络·算法·架构
SamDeepThinking2 小时前
并发量就算只有2,该上锁还得上呀
java·后端·架构
Sam_Deep_Thinking3 小时前
如何让订单系统和营销系统解耦
java·架构·系统架构
ting94520003 小时前
Micro1 超详细深度解析:架构原理、部署实战、性能评测与落地应用全指南
人工智能·架构
该昵称用户已存在3 小时前
从边缘计量到碳足迹追踪:MyEMS 开源一体化架构的全栈拆解
架构·开源
福大大架构师每日一题4 小时前
ollama v0.22.1 重大更新全解析:新增Poolside集成、模型推荐机制与多架构适配
架构·ollama
该昵称用户已存在4 小时前
以开源筑基,架构先行——深度拆解 MyEMS 微服务能源管理系统的技术内核
微服务·架构·开源
生成论实验室5 小时前
《事件关系阴阳博弈动力学:识势应势之道》第一篇:生成正在发生——从《即事经》到事件-关系网络
人工智能·科技·算法·架构·创业创新
:mnong6 小时前
打造 AI 级 Agent 架构
人工智能·架构