前言

阅读 Agent 相关内容时,经常会遇到两个概念:PlanActReAct

一开始看这两个概念,很容易觉得它们只是 Agent 执行过程中的两种写法。但如果把它们放到真实业务系统中,例如客服工单、订单售后、会员补发、退款审批等场景,就会发现它们背后其实代表了两种不同的 Agent 设计思想。

简单来说:

PlanAct 解决的是:任务流程如何稳定执行。
ReAct 解决的是:未知问题如何动态调查。

在实际工程中,如果只使用 PlanAct,系统虽然稳定,但遇到异常时容易僵硬;如果只使用 ReAct,系统虽然灵活,但容易不可控、成本高、路径不稳定。

因此,在企业级 Agent 应用中,更推荐的做法是:

外层使用 PlanAct 控制主流程,内层使用 ReAct 处理异常分支。

一、为什么需要 PlanAct + ReAct?

在传统客服系统中,大多数问题都是通过固定流程处理的。

例如用户提交工单:

我昨天买了会员,但是今天账号还是普通用户,请帮我处理。

一个成熟客服系统通常不会直接让人工客服自由发挥,而是会有一套 SOP:

1. 查询用户订单
2. 查询支付状态
3. 查询会员状态
4. 判断是否满足补发条件
5. 补发会员或转人工
6. 回复用户
7. 关闭工单

这就是典型的流程驱动

但是在真实业务中,问题往往没有这么简单。

例如可能出现:

订单不存在,但用户说已经付款
支付成功,但会员系统没有发放记录
用户用 A 邮箱付款,却登录了 B 账号
第三方支付回调失败
用户之前已经提交过相同工单
订单被风控系统拦截

这些异常情况很难完全用固定流程覆盖。

因此,我们需要两类能力:

能力

解决的问题

PlanAct

标准流程如何稳定、可控地执行

ReAct

异常问题如何动态调查、逐步推理

PlanAct + ReAct

标准问题自动处理,复杂问题智能调查

这也是本文讨论 PlanAct + ReAct 的核心原因。


二、PlanAct 是什么?

PlanAct 可以拆成两个部分:

Plan + Act

也就是:

先计划,再执行

它的核心思想是:

在任务开始前,先生成或加载一个明确的执行计划,然后按照计划一步步执行。

PlanAct 的执行过程可以理解为:

在客服系统中,PlanAct 通常不是让模型自由生成计划,而是加载业务侧预先定义好的 SOP。

例如“会员未到账”工单,可以定义如下 SOP:

1. 确认用户身份
2. 查询最近 7 天会员订单
3. 判断订单是否存在
4. 查询支付状态
5. 查询会员权益状态
6. 判断是否满足补发条件
7. 执行补发或转人工
8. 生成客服回复
9. 关闭工单

这就是一个典型的 PlanAct 过程。

2.1 PlanAct 的本质

PlanAct 的本质是:

流程控制权在外部系统,而不是完全交给模型。

模型可以参与其中,例如:

识别工单类型
生成客服回复
总结工单原因
判断用户情绪
提取关键信息

但整体流程由工作流或 SOP 控制。

如果用 Dify 来理解,Dify 工作流天然就是一种 PlanAct 思想:

开始节点
↓
LLM 分类节点
↓
HTTP 查询节点
↓
条件分支节点
↓
LLM 回复节点
↓
结束节点

这里的工作流就是 Plan,节点执行就是 Act。


三、ReAct 是什么?

ReAct 是 Reasoning + Acting 的组合。

它的核心流程是:

Reason → Act → Observe → Reason → Act → Observe

中文可以理解为:

推理 → 行动 → 观察 → 再推理 → 再行动 → 再观察

ReAct 的执行过程如下:

ReAct 和 PlanAct 最大的区别在于:

ReAct 不要求一开始就有完整计划,而是让模型根据当前观察结果动态决定下一步。

例如用户提交工单:

我付款了,但是会员没到账。

ReAct Agent 可能会这样处理:

推理:用户说付款了但没到账,先查询订单。
行动:调用订单查询接口。
观察:发现订单支付成功。

推理:订单支付成功但会员没到账,继续查询会员状态。
行动:调用会员状态接口。
观察:用户仍然是普通用户。

推理:可能是权益发放失败,查询发放日志。
行动:调用权益日志接口。
观察:权益发放失败,原因是回调超时。

推理:符合补发条件,可以补发会员。
行动:调用会员补发接口。
观察:补发成功。

推理:问题已解决,回复用户。

这里每一步都依赖上一步的观察结果。

3.1 ReAct 的本质

ReAct 的本质是:

模型根据当前上下文动态决定下一步行动。

这意味着 ReAct 更像一个会调查问题的智能体,而不是一个固定流程执行器。

它特别适合:

问题原因未知
输入信息不完整
需要查询多个系统
每一步结果会影响下一步
异常情况较多
无法提前枚举所有路径

例如:

复杂投诉处理
支付异常排查
账号异常申诉
订单状态不一致
物流多节点异常
系统故障诊断

四、PlanAct 和 ReAct 的原理区别

PlanAct 和 ReAct 表面上都是 Agent 执行模式,但它们背后的控制逻辑完全不同。

4.1 控制权不同

PlanAct 的控制权在流程系统:

流程决定下一步做什么。

ReAct 的控制权在模型:

模型根据观察结果决定下一步做什么。

可以用下面这张图理解:

4.2 上下文形态不同

PlanAct 的上下文更像一个状态机。

例如:

{
  "ticket_id": "T1001",
  "user_id": "U1001",
  "ticket_type": "membership_not_received",
  "current_step": "query_payment",
  "order_status": "success",
  "membership_status": "normal",
  "solution": "grant_membership"
}

ReAct 的上下文更像一个行动观察日志。

例如:

{
  "goal": "调查会员未到账原因",
  "actions": [
    {
      "step": 1,
      "action": "query_order",
      "observation": "订单存在,支付成功"
    },
    {
      "step": 2,
      "action": "query_membership",
      "observation": "用户仍为普通用户"
    },
    {
      "step": 3,
      "action": "query_grant_log",
      "observation": "权益发放失败,原因是回调超时"
    }
  ],
  "current_thought": "支付成功但权益发放失败,可以补发会员"
}

简单来说:

模式

上下文形态

PlanAct

结构化状态

ReAct

行动观察日志

PlanAct + ReAct

主流程状态 + 异常调查日志


五、PlanAct、ReAct、PlanAct + ReAct 对比

5.1 核心对比表

对比维度

PlanAct

ReAct

PlanAct + ReAct

核心思想

先计划,再执行

边推理,边行动,边观察

主流程计划执行,异常分支动态调查

控制权

流程 / SOP

模型

流程为主,模型为辅

执行路径

相对固定

动态变化

主路径固定,异常路径动态

上下文形态

结构化状态

行动观察日志

状态 + 调查日志

优点

稳定、可控、可审计

灵活、适应复杂问题

兼顾稳定性和灵活性

缺点

遇到异常容易僵硬

容易绕路、成本高、不可控

架构复杂度更高

适合场景

标准业务流程

异常排查、复杂问题

企业级 Agent 系统

工程实现

Dify 工作流、LangGraph 流程图

Agent Loop、工具调用循环

工作流 + Agent 子流程

风险控制

容易做

较难做

可在主流程中做边界控制

5.2 适用场景对比表

业务场景

推荐模式

原因

会员未到账标准处理

PlanAct

有明确 SOP

退款申请

PlanAct

规则清晰,需要审计

发票申请

PlanAct

流程稳定

物流查询

PlanAct

接口查询路径固定

支付成功但订单不存在

ReAct

需要动态排查支付流水、回调日志

用户多账号冲突

ReAct

需要根据观察结果不断查询

复杂投诉处理

ReAct

问题路径不确定

客服自动化系统

PlanAct + ReAct

标准问题自动处理,异常问题动态调查

企业内部审批 Agent

PlanAct + ReAct

主流程固定,但异常材料需要分析

六、客服工单场景设计

下面以“会员未到账”工单为例,设计一个 PlanAct + ReAct 混合系统。

6.1 用户工单

我昨天买了会员,但是今天账号还是普通用户,请帮我处理。

6.2 可用工具

classify_ticket(content)           识别工单类型
query_order(user_id)               查询用户订单
query_payment(order_id)            查询支付状态
query_membership(user_id)          查询会员状态
grant_membership(user_id)          补发会员权益
query_grant_log(order_id)          查询权益发放日志
query_callback_log(order_id)       查询支付回调日志
query_user_by_email(email)         根据邮箱查询用户
query_history_ticket(user_id)      查询历史工单
escalate_to_human(ticket_id)       转人工
reply_user(ticket_id, content)     回复用户

6.3 整体设计思想

主流程使用 PlanAct:

识别工单类型
查询订单
查询支付状态
查询会员状态
判断是否补发
生成回复
关闭工单

异常分支使用 ReAct:

订单不存在
支付状态未知
订单和用户不匹配
会员状态异常
权益发放日志异常
工具调用失败
模型判断不确定

整体流程如下:

七、PlanAct 实现:标准流程处理

PlanAct 适合处理标准工单。

例如:

订单存在
支付成功
会员状态仍为普通用户

这种情况可以直接补发会员。

7.1 PlanAct 流程图

7.2 PlanAct 伪代码

def planact_customer_service(ticket):
    context = {
        "ticket": ticket,
        "state": {},
        "result": None
    }

    # Step 1:识别工单类型
    ticket_type = classify_ticket(ticket["content"])
    context["state"]["ticket_type"] = ticket_type

    if ticket_type != "membership_not_received":
        escalate_to_human(ticket["ticket_id"])
        return context

    # Step 2:查询订单
    order = query_order(ticket["user_id"])
    context["state"]["order"] = order

    if order is None:
        context["state"]["need_react"] = True
        context["state"]["react_reason"] = "未查询到会员订单"
        return context

    # Step 3:查询支付状态
    payment = query_payment(order["order_id"])
    context["state"]["payment"] = payment

    # Step 4:查询会员状态
    membership = query_membership(ticket["user_id"])
    context["state"]["membership"] = membership

    # Step 5:标准规则判断
    if payment["status"] == "success" and membership["status"] == "normal":
        grant_result = grant_membership(ticket["user_id"])
        context["state"]["grant_result"] = grant_result

        reply = generate_reply({
            "type": "membership_granted",
            "order": order,
            "payment": payment,
            "membership": membership,
            "grant_result": grant_result
        })

        reply_user(ticket["ticket_id"], reply)
        context["result"] = reply
        return context

    if membership["status"] == "vip":
        reply = generate_reply({
            "type": "already_active",
            "membership": membership
        })

        reply_user(ticket["ticket_id"], reply)
        context["result"] = reply
        return context

    if payment["status"] != "success":
        reply = generate_reply({
            "type": "payment_failed",
            "payment": payment
        })

        reply_user(ticket["ticket_id"], reply)
        context["result"] = reply
        return context

    # 标准规则无法覆盖,交给 ReAct
    context["state"]["need_react"] = True
    context["state"]["react_reason"] = "标准规则无法判断"
    return context

八、ReAct 实现:异常问题调查

ReAct 适合处理标准流程无法覆盖的问题。

例如:

订单查不到,但用户提供了支付截图
支付成功,但订单系统没有记录
权益发放失败,但失败原因未知
订单 user_id 为空,只有 email
用户存在多个账号

8.1 ReAct 异常调查流程图

8.2 ReAct Prompt 示例

你是一个客服工单异常调查 Agent。

你的目标:
调查会员未到账的真实原因,并给出下一步处理建议。

你可以使用以下工具:
1. query_order(user_id):查询订单
2. query_payment(order_id):查询支付状态
3. query_membership(user_id):查询会员状态
4. query_grant_log(order_id):查询权益发放日志
5. query_callback_log(order_id):查询支付回调日志
6. query_user_by_email(email):根据邮箱查询用户
7. query_history_ticket(user_id):查询历史工单
8. final_answer:输出最终调查结果
9. need_human:转人工

当前工单:
{{ticket}}

已知状态:
{{known_state}}

历史行动与观察:
{{observations}}

请根据当前信息判断下一步动作。

要求:
1. 只输出 JSON
2. 不要输出无关解释
3. 如果信息不足,选择一个最有价值的工具继续查询
4. 如果已经能够判断原因,输出 final_answer
5. 如果风险较高或无法判断,输出 need_human

输出格式:
{
  "thought": "当前推理",
  "action": "工具名称",
  "action_input": {},
  "reason": "选择该动作的原因"
}

8.3 ReAct 伪代码

def react_investigate(ticket, parent_context):
    react_context = {
        "goal": "调查会员未到账异常原因",
        "ticket": ticket,
        "known_state": parent_context["state"],
        "actions": [],
        "observations": []
    }

    max_steps = 5

    for step in range(max_steps):
        prompt = build_react_prompt(react_context)
        decision = llm(prompt)

        action = decision["action"]
        action_input = decision.get("action_input", {})

        react_context["actions"].append({
            "step": step,
            "thought": decision.get("thought"),
            "action": action,
            "input": action_input,
            "reason": decision.get("reason")
        })

        if action == "final_answer":
            return {
                "status": "resolved",
                "reason": decision.get("reason"),
                "suggestion": decision.get("suggestion"),
                "reply": decision.get("reply"),
                "context": react_context
            }

        if action == "need_human":
            return {
                "status": "need_human",
                "reason": decision.get("reason"),
                "context": react_context
            }

        observation = call_tool(action, action_input)

        react_context["observations"].append({
            "step": step,
            "action": action,
            "observation": observation
        })

    return {
        "status": "need_human",
        "reason": "ReAct 调查超过最大步数",
        "context": react_context
    }

九、PlanAct + ReAct 混合实现

在实际项目中,推荐采用混合模式。

整体思想是:

先走标准流程。
标准流程能解决,直接解决。
标准流程不能解决,进入 ReAct 异常调查。
ReAct 能解决,回到主流程处理。
ReAct 不能解决,转人工。

9.1 混合模式流程图

9.2 混合模式伪代码

def hybrid_customer_service_agent(ticket):
    context = {
        "ticket": ticket,
        "state": {},
        "mode": "planact"
    }

    # 1. 先执行 PlanAct 主流程
    plan_result = planact_customer_service(ticket)
    context["state"].update(plan_result["state"])

    # 2. 如果 PlanAct 已经解决,直接返回
    if plan_result.get("result"):
        context["result"] = plan_result["result"]
        return context

    # 3. 如果 PlanAct 无法解决,进入 ReAct
    if context["state"].get("need_react"):
        context["mode"] = "react"

        investigation = react_investigate(ticket, context)
        context["state"]["investigation"] = investigation

        # 4. ReAct 得出可解决结论
        if investigation["status"] == "resolved":
            if is_low_risk(investigation):
                execute_result = execute_suggestion(investigation)
                context["state"]["execute_result"] = execute_result

                reply = investigation.get("reply") or generate_reply({
                    "type": "react_resolved",
                    "investigation": investigation,
                    "execute_result": execute_result
                })

                reply_user(ticket["ticket_id"], reply)
                context["result"] = reply
                return context

            # 高风险动作需要人工确认
            escalate_to_human(ticket["ticket_id"])
            context["state"]["escalate_reason"] = "ReAct 建议涉及高风险动作"
            return context

        # 5. ReAct 无法解决,转人工
        if investigation["status"] == "need_human":
            escalate_to_human(ticket["ticket_id"])
            context["state"]["escalate_reason"] = investigation["reason"]
            return context

    # 6. 兜底转人工
    escalate_to_human(ticket["ticket_id"])
    context["state"]["escalate_reason"] = "未知异常"
    return context

十、Dify 中如何落地 PlanAct + ReAct?

如果使用 Dify,可以把系统拆成两个部分:

Dify 工作流主干:PlanAct
Agent 节点 / 子流程:ReAct

10.1 Dify 主流程设计

10.2 Dify 变量设计

建议在 Dify 中维护以下变量:

变量名

含义

ticket_id

工单 ID

user_id

用户 ID

ticket_content

工单内容

ticket_type

工单类型

order_result

订单查询结果

payment_result

支付查询结果

membership_result

会员状态

planact_status

主流程执行状态

need_react

是否进入 ReAct

react_reason

进入 ReAct 的原因

investigation_result

ReAct 调查结果

final_reply

最终客服回复

10.3 ReAct 节点输出格式

ReAct 节点建议输出结构化 JSON:

{
  "status": "resolved",
  "reason": "支付成功但权益发放回调失败",
  "suggestion": {
    "action": "grant_membership",
    "risk_level": "low",
    "params": {
      "user_id": "U1001",
      "product": "monthly_vip"
    }
  },
  "reply": "您好,已为您核实,您的会员订单支付成功,但权益发放出现延迟。我们已为您补发会员,请刷新后查看。"
}

如果无法解决,则输出:

{
  "status": "need_human",
  "reason": "用户存在多个账号,无法确认订单归属,需要人工核验"
}

十一、上下文工程设计

PlanAct + ReAct 的关键不是简单调用模型,而是上下文如何组织。

11.1 PlanAct 上下文

PlanAct 上下文强调结构化状态:

{
  "goal": "处理会员未到账工单",
  "current_step": "query_membership",
  "ticket": {
    "ticket_id": "T1001",
    "user_id": "U1001",
    "content": "我昨天买了会员,但是今天账号还是普通用户"
  },
  "state": {
    "ticket_type": "membership_not_received",
    "order_exists": true,
    "payment_status": "success",
    "membership_status": "normal"
  }
}

它关心的是:

当前流程走到哪一步?
每一步结果是什么?
是否满足分支条件?
下一步应该进入哪个节点?

11.2 ReAct 上下文

ReAct 上下文强调历史行动和观察:

{
  "goal": "调查会员未到账异常原因",
  "known_state": {
    "order_exists": false,
    "user_id": "U1001"
  },
  "actions": [
    {
      "step": 1,
      "thought": "订单不存在,但用户声称已付款,应查询支付流水",
      "action": "query_payment_log",
      "observation": "存在一笔成功支付记录"
    },
    {
      "step": 2,
      "thought": "支付存在但订单不存在,应查询支付回调日志",
      "action": "query_callback_log",
      "observation": "回调失败,错误码 TIMEOUT"
    }
  ]
}

它关心的是:

已经查过什么?
观察到了什么?
当前推理是什么?
下一步最有价值的动作是什么?
是否已经可以得出结论?

11.3 混合上下文

PlanAct + ReAct 最推荐的上下文结构是:

{
  "ticket": {},
  "planact_state": {},
  "react_investigation": {},
  "final_decision": {}
}

也就是:

主流程状态和异常调查日志分开存储。

这样做的好处是:

方便调试
方便审计
方便回放
方便优化 prompt
方便人工接管

十二、工程实践注意点

12.1 ReAct 必须限制最大步数

ReAct 不能无限循环。

建议设置:

max_steps = 3  # 简单异常
max_steps = 5  # 中等复杂异常
max_steps = 8  # 复杂调查,不建议更高

超过最大步数后,直接转人工。

12.2 工具权限必须分级

不要把所有工具都开放给模型。

建议分成三类:

工具类型

示例

是否可自动执行

只读工具

查询订单、查询支付、查询会员

可以

低风险写工具

写备注、生成回复、关闭普通工单

可以

高风险写工具

退款、赔付、补偿、账号解封

需要规则校验或人工确认

12.3 高风险动作要有规则兜底

例如补发会员可以设置规则:

订单必须存在
支付必须成功
当前会员状态必须未开通
最近 30 天没有重复补发
补发金额或权益等级不能超过订单商品

如果不满足规则,则不能让模型直接执行。

12.4 输出必须结构化

ReAct 的输出不要只给自然语言。

推荐统一 JSON:

{
  "status": "resolved",
  "reason": "支付成功但权益未发放",
  "suggestion": {
    "action": "grant_membership",
    "risk_level": "low"
  },
  "reply": "您好,已为您补发会员。"
}

结构化输出可以让主流程继续判断,而不是依赖人工读模型回答。

12.5 必须保留人工兜底

Agent 不是为了替代所有人工,而是优先处理高频、低风险、规则明确的问题。

以下情况建议转人工:

用户身份不一致
订单归属不明确
支付状态异常
涉及退款、赔付、投诉升级
模型置信度不足
工具调用失败
超过最大调查步数

十三、从 Dify 视角重新理解 PlanAct + ReAct

如果你正在使用 Dify + 工作流 + 模型,可以这样理解:

Dify 组件

对应 Agent 设计

工作流节点

PlanAct 的步骤

条件分支

PlanAct 的流程控制

HTTP 请求节点

工具调用

LLM 节点

局部推理或文本生成

变量

上下文状态

Agent 节点

ReAct 动态调查

知识库检索

上下文补充

人工转接

安全兜底

所以,Dify 工作流天然偏 PlanAct。

如果想让它具备更强的 Agent 能力,可以在关键节点引入 ReAct:

标准路径:Dify 工作流稳定执行
异常路径:Agent 节点动态调查
高风险路径:转人工确认

这是一种比较务实的企业级 Agent 落地方式。


十四、总结

PlanAct 和 ReAct 不是谁替代谁的关系,而是互补关系。

PlanAct 的价值在于:

稳定
可控
可审计
适合标准业务流程

ReAct 的价值在于:

灵活
动态
适合异常调查
适合信息不完整的问题

在客服工单场景中,最推荐的设计是:

PlanAct 做主流程
ReAct 做异常调查
人工做最终兜底

最终系统可以形成这样的能力:

标准问题自动处理
异常问题智能调查
高风险问题人工确认
完整链路可追踪

一句话总结:

PlanAct 让 Agent 按流程稳定做事,ReAct 让 Agent 在遇到未知问题时会调查。二者结合,才更接近真实可用的企业级 Agent。

更多推荐