AI 纪元 追踪人工智能最新进展,深度解读前沿技术

Agent 卡在循环里,先把重试权交给状态机

长链路 Agent 容易在工具调用之间反复打转。把重试次数、错误信息和人工审批写进状态,再用 LangGraph 的条件边控制去向,可以让修复循环在达到上限后退出,转入人工处理或降级流程。

0 次阅读 约 3607 字 7 分钟 AI 生成
A minimalist editorial still life of a mechanical traffic light or switch mechanism controlling a path, symbolizing state management and loop control.
A minimalist editorial still life of a mechanical traffic light or switch mechanism controlling a path, symbolizing state management and loop control.

先看一个售后测试场景。

2026 年初,某跨境电商测试一款基于 GPT-4o 的售后 Agent。用户确认退货地址后突然改口:“等等,我刚才填的邮编好像错了,能改一下吗?”

Agent 先调用“修改地址”,随后调用“核对地址”。问题出在上下文里同时留着新旧两个邮编。模型把新邮编判断成“与之前的信息不匹配”,再次要求用户确认。用户回复“对,就是用新的”,流程又回到修改和核对。

3 分钟内,两个工具来回调用了 24 次,直到该单的 Token 额度耗尽,并触发 API Rate Limit。

这个循环暴露的缺口很具体:系统没有独立记录当前有效邮编、已经重试多少次,以及何时必须退出。所有判断都压在模型的一次次生成上。

Prompt 里的“最多两次”不算上限

开发时最常见的补丁,是在 System Prompt 里加一句:如果用户修改地址,请先核对;如果核对失败,不要重复调用修改接口超过 2 次。

这句话只是一段等待模型理解的文本。它没有真正的计数器,也不会在第三次调用前自动拦截。

随着历史对话、工具返回的 JSON 和报错信息不断累积,模型可能在某一轮重新解释规则。继续往 Prompt 里追加例外,只会让输入变长,仍然没有程序级的出口。

此前关于反思闭环的讨论提出过一个实用做法:把生成、评估和修正拆开。这个思路能减少一次生成包办所有判断的问题,但只要修正节点可以无限回流,死循环依然存在。

退出条件需要写进代码。

把跳转权从模型手里拿出来

有限状态机(Finite State Machine,FSM)放进 Agent 工作流后,主要管理三类东西:

  • 状态(State):保存对话、当前业务数据、错误信息、重试次数和审批结果。
  • 节点(Node):完成一个明确动作,例如生成代码、执行编译、修改地址或等待人工审核。
  • 条件边(Conditional Edge):检查状态,再决定流程进入哪个节点。

模型可以在修复节点里决定代码怎么改,但它不能决定自己是否还有第四次修复机会。后者交给一行明确的 Python 判断,例如 compile_retry_count >= 3。

这一区分在带有副作用的流程中尤其重要。Agent 一旦可以调用工具、使用权限、发起支付或写回系统,节点跳转就不只是对话选择。地址可能被重复修改,支付也可能被重复发起。状态机负责守住流程边界,重要操作则可以停在人工审核节点,等待 Human-in-the-loop 确认。

用 LangGraph 给循环装一个出口

下面沿用从 PRD 到测试脚本的场景:Agent 先生成 API 测试脚本,再交给编译器执行。编译失败时允许模型修复,但最多重试 3 次。

先定义全局状态:

from typing import TypedDict, List, Annotated
import operator

class AgentState(TypedDict):
    messages: Annotated[List[dict], operator.add]
    code_snippet: str
    error_message: str
    compile_retry_count: int
    is_approved: bool

compile_retry_count 要在任务开始时初始化为 0。它是流程的硬计数器,不需要模型从对话里猜测已经重试了几次。

编译结束后,由路由函数决定下一步:

def route_after_compile(state: AgentState) -> str:
    if not state['error_message']:
        return 'human_approval'

    if state['compile_retry_count'] >= 3:
        return 'fallback_error'

    return 'fix_code'

修复节点必须同步增加计数。漏掉这一步,状态图看起来有上限,运行时仍可能一直循环。

def fix_code_fn(state: AgentState) -> dict:
    fixed_code = ...  # 调用模型,根据 error_message 修复代码

    return {
        'code_snippet': fixed_code,
        'compile_retry_count': state['compile_retry_count'] + 1,
    }

随后把节点和条件边接入 LangGraph:

from langgraph.graph import StateGraph, END

workflow = StateGraph(AgentState)

workflow.add_node('generate_code', generate_code_fn)
workflow.add_node('compile_code', compile_code_fn)
workflow.add_node('fix_code', fix_code_fn)
workflow.add_node('fallback_error', fallback_error_fn)
workflow.add_node('human_approval', human_approval_fn)

workflow.set_entry_point('generate_code')
workflow.add_edge('generate_code', 'compile_code')

workflow.add_conditional_edges(
    'compile_code',
    route_after_compile,
    {
        'human_approval': 'human_approval',
        'fix_code': 'fix_code',
        'fallback_error': 'fallback_error',
    },
)

workflow.add_edge('fix_code', 'compile_code')
workflow.add_edge('fallback_error', END)
workflow.add_edge('human_approval', END)

app = workflow.compile()

fix_code -> compile_code 依然是一个循环,但它已经有了明确上限。编译成功,流程进入人工审核;连续修复 3 次仍然失败,流程进入 fallback_error,保留错误信息并停止继续调用模型。

这里把 human_approval 视为完成审批后结束的节点。如果业务还要区分通过与驳回,可以再为它增加一条条件边。

状态里要放能决定去向的数据

很多 Agent 只保存 messages,真正影响流程的数据仍然埋在对话里。这样一来,每次路由都要让模型重新阅读历史并做判断,旧值和新值很容易混在一起。

售后地址场景可以单独保存 confirmed_postcode。用户确认新邮编后,程序直接更新这个字段;旧邮编可以留在消息历史中,但后续工具调用只读取已经确认的结构化值。

编译修复场景至少要保留这些字段:

  • error_message:判断编译是否成功,也为下一轮修复提供输入。
  • compile_retry_count:控制循环上限。
  • code_snippet:确保编译器拿到的是当前版本。
  • is_approved:控制需要人工确认的节点能否继续执行。

排查故障时,日志也应记录当前节点、路由结果、重试次数和错误信息。看到 compile_code -> fix_code -> compile_code 的状态快照,通常比先翻完整 Prompt 更容易找到问题。

状态机能管住什么,管不住什么

状态机能限制循环次数,让执行路径可追踪,也能在付款、退款审核和重要数据修改前挂起流程。它不会让每次模型调用消耗完全相同的 Token,也不能保证模型一定修好代码。

工具接口本身仍要处理幂等和权限校验。状态机可以阻止流程无限回转,但如果“发起支付”接口允许同一请求重复生效,风险仍然存在。

更稳妥的分工是:模型负责理解用户、生成方案和修复内容;代码负责重试次数、审批条件、权限边界和终止状态。这样做没有消除模型的随机性,只是把它限制在一条能退出、能追踪的路径里。

相关阅读