基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

如果大模型陷入了“逻辑死胡同”,如何防止工作流出现无限死循环(Infinite Loop)?

知识点图片

在LangGraph中,大模型(尤其是在使用ReAct架构或Agent模式时)陷入“逻辑死胡同”(例如:反复调用同一个报错的工具、在两个状态之间来回横跳)是一个非常常见的问题。

为了防止这种无限死循环,LangGraph 提供了从框架底层业务逻辑层的多重防御机制。以下是解决这个问题的五种核心方法,建议组合使用(深度防御):


1. 框架自带防御:设置 recursion_limit(最基础的底线)

LangGraph 在执行图(Graph)时,默认带有一个递归深度限制(默认通常是 25)。如果状态流转次数超过这个限制,框架会直接抛出 GraphRecursionError

你可以根据业务的复杂程度,在调用 invokestream 时手动调整这个限制,并捕获异常:

python
from langgraph.errors import GraphRecursionError

try:
    # 设置最大递归/循环次数为 10
    result = app.invoke(inputs, {"recursion_limit": 10})
except GraphRecursionError:
    print("大模型陷入了死循环,已强制中断!")
    # 这里可以加入备用逻辑,比如返回一个友好的错误提示给用户

优点:实现成本为0,绝对能防止无限消耗Token。
缺点:属于“硬着陆”,直接抛出异常,不够优雅,无法在图的内部做平滑的错误恢复。


2. 状态级防御:引入“步数计数器”(最推荐的优雅做法)

为了实现“软着陆”,最好的方法是在你的 State(状态字典)中增加一个计数器,并使用条件边(Conditional Edge)来判断是否应该强制结束。

实现步骤:

  1. 在 State 中增加 step_count
  2. 在核心节点(例如 Agent 节点)中让 step_count + 1。
  3. 在路由(Router)节点判断:如果超过阈值,直接路由到 END 或特定的“降级处理节点”。
python
from typing import TypedDict, Annotated
import operator
from langgraph.graph import StateGraph, END

# 1. 定义状态,包含计数器
class AgentState(TypedDict):
    messages: list
    step_count: Annotated[int, operator.add] # 每次自动累加

# 2. 节点逻辑
def agent_node(state: AgentState):
    # 调用大模型...
    response = llm.invoke(state["messages"])
    # 返回消息,并让 step_count + 1
    return {"messages": [response], "step_count": 1} 

# 3. 条件边判断逻辑
def should_continue(state: AgentState):
    # 如果步数超过 5 步,强制结束或进入错误处理节点
    if state.get("step_count", 0) >= 5:
        return "force_end" # 或者直接返回 END
    
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "continue"
    return "end"

# 4. 构建图
workflow = StateGraph(AgentState)
workflow.add_node("agent", agent_node)
# ... 其他节点 ...

workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "continue": "tools",
        "end": END,
        "force_end": END # 强制打断死循环
    }
)

3. 工具级防御:检测“重复的错误尝试”

模型陷入死胡同,往往是因为它不断用相同的错误参数调用同一个工具。可以在图的状态中记录“历史工具调用”,一旦检测到重复,立刻阻断。

实现思路:

  • 在 State 中维护一个列表 past_tool_calls: list[str]
  • 在执行工具前,将 工具名+参数 拼接成字符串(或哈希值)并检查是否已存在于 past_tool_calls 中。
  • 如果发现重复:不要去执行工具,而是由一个专门的节点直接给大模型返回一条强硬的系统消息:“警告:你正在使用完全相同的参数重复调用该工具,请立即停止这种尝试,更换策略或直接回答用户。”

4. Prompt级防御:强化 System Prompt 与 Fallback

大模型本身并不知道自己循环了多少次,所以需要通过 Prompt 给它提供“空间感”和规则。

  • 添加强制指令:在 System Prompt 中明确规定:“如果连续 3 次调用工具失败,请放弃并告诉用户无法完成。”
  • 状态注入 Prompt:把当前步数喂给模型。例如在每次构建 Prompt 时加入提示:“当前这是你的第 {step_count} 步思考。请注意,最多只能思考 5 步,请尽快得出结论。”
  • 模型降级(Fallback):如果你使用的是较小的模型(如 Llama-3-8B),它更容易陷入死循环。可以在捕获到即将死循环时,将最后一次请求Fallback 到更聪明的模型(如 GPT-4o 或 Claude-3.5-Sonnet),让聪明的模型来打破僵局。

5. 人工介入:Human-in-the-loop (HITL)

如果这个工作流执行的是非常重要或高成本的任务,你可以利用 LangGraph 的断点功能(Breakpoints)。
当系统检测到逻辑可能陷入循环(例如 step_count == 3 时),暂停图的执行,将当前状态抛给人类审批。

python
# 编译图的时候设置中断点
app = workflow.compile(
    checkpointer=memory,
    interrupt_before=["suspicious_loop_node"] # 比如当步数达到警告值时路由到的节点
)

人类可以查看状态,如果发现模型钻了牛角尖,可以通过 app.update_state() 手动修改状态,给模型注入一句正确的提示,然后再恢复图的运行。

总结建议:

在实际生产中,建议采用 “1 + 2 + 4” 组合拳

  1. 底层用 recursion_limit 兜底防崩溃。
  2. 业务层用 step_count 结合 Conditional Edge 做软性拦截,优雅退出。
  3. 在 Prompt 中注入步数感知,引导大模型自行脱困。
00:00
00:00