智能体任务拆解与执行:基于ReAct+Plan-and-Execute框架的行业Skill构建实录

一、引言:当"一步到位"失效时

过去一年,我一直在做一件事:把行业里那些重复、繁琐、依赖经验判断的业务流程,交给大语言模型(LLM)智能体去执行。最初的想法很简单------写一个精心设计的Prompt,把任务描述清楚,让模型直接给出结果。

结果并不理想。面对"核对本月供应商账单与采购订单差异并生成调整建议"这类真实任务,模型要么在一个漫长的单次推理中逐渐"迷失",要么在第一步就选错了工具,要么在中间某一步失败后整条链路崩溃。问题的根源在于:复杂任务的解决路径不是线性的,而模型的一次性生成天然是线性的

这让我转向了智能体领域两个经典且互补的框架------ReAct (Reasoning + Acting)与 Plan-and-Execute。在经历多轮重构之后,我沉淀出一套"行业Skill"的构建方法论。这篇文章是这段实践的完整记录,包含框架原理、结合策略、可复用的代码骨架,以及踩过的坑。

二、两个框架的原理与取舍

2.1 ReAct:让"想"与"做"交替进行

ReAct的核心思想是:让模型在每一步决策时先输出Thought(推理),再输出Action(行动),观察环境返回的Observation(观察),如此循环,直到任务完成。它把"推理过程"外化到对话上下文里,使得模型每一步都基于最新的真实反馈做决策,而不是闭门造车。

用代码来表达,一个最小化的ReAct循环如下:

python 复制代码
def react_loop(task: str, tools: dict, max_steps: int = 10):
    messages = [{"role": "user", "content": f"任务:{task}\n可用工具:{list(tools.keys())}"}]
    for _ in range(max_steps):
        resp = llm_chat(messages)
        messages.append({"role": "assistant", "content": resp})
        action = parse_action(resp)          # 从响应中解析出 Action
        if action.name == "Finish":
            return action.argument
        if action.name not in tools:
            observation = f"工具 {action.name} 不存在"
        else:
            observation = tools[action.name](action.argument)
        messages.append({"role": "user", "content": f"Observation:{observation}"})
    raise TimeoutError("达到最大步数")

这个循环简单、透明、易于调试,是绝大多数Agent框架(如LangChain的AgentExecutor、ReAct Agent)的底层形态。它的优势在于动态性 ------模型可以根据中间结果随时调整下一步;劣势在于长链路效率低------每一步都消耗Token,步骤一多,上下文变长,模型反而更容易在历史里"迷路",而且没有全局视角,容易出现"局部最优、整体次优"。

2.2 Plan-and-Execute:先有蓝图,再动工

Plan-and-Execute则把流程切成两个角色:Planner (规划者)与 Executor(执行者)。Planner先对任务做整体拆解,产出一份带依赖关系的步骤清单;Executor按清单逐条执行,执行结果回填给Planner,Planner根据反馈修订剩余计划。

python 复制代码
class PlanExecutor:
    def __init__(self, planner: LLM, executor: LLM):
        self.planner, self.executor = planner, executor

    def run(self, task: str) -> str:
        plan = self.planner.make_plan(task)          # 生成结构化计划
        results = {}
        while plan.has_pending():
            step = plan.next_step()                  # 取下一个可执行步骤
            out = self.executor.execute(step, results)  # 执行并引用前置结果
            results[step.id] = out
            plan = self.planner.replan(task, plan, results)  # 必要时修订
        return plan.final_answer(results)

它的好处是全局可控 :计划先行意味着任务被拆解为可验证的单元,每个步骤可以独立测试、独立重试、独立审计------这正是行业落地最看重的能力。代价是灵活性不足:真实世界里计划赶不上变化,步骤之间隐藏的依赖、执行中的意外,都需要额外机制兜底。

2.3 两者不是二选一,而是互补

我把两者的差异归纳为一张决策表:

维度 ReAct Plan-and-Execute
推理粒度 逐行动 整任务规划
灵活性 高,随机应变 中,依赖修订机制
长任务稳定性 低,易漂移 高,蓝图约束
Token成本
可观测性 过程日志 计划+执行日志
适用场景 探索性、工具选择类任务 流程固定、步骤可拆的任务

行业任务------对账、合规审查、客户工单处理、报告生成------绝大多数属于"流程相对固定但细节充满变化"的形态。它们既需要Plan-and-Execute的结构约束来保证不跑偏,又需要ReAct的即时反馈来处理数据层面的意外。最终方案是混合:用Plan-and-Execute做骨架,在每一个执行步骤内部,用ReAct循环做细粒度推理。

三、行业Skill的抽象设计

所谓"行业Skill",我把它定义为:面向特定业务场景、可复用的、封装了任务拆解与执行逻辑的能力单元。它像函数一样有输入输出契约,内部则可能是一整套Agent编排。

一个Skill的接口设计如下:

python 复制代码
from pydantic import BaseModel, Field
from typing import Any, Callable

class SkillSpec(BaseModel):
    name: str
    description: str                              # 供Planner选Skill时使用
    input_schema: dict                            # 入参契约,如 {"account_id": "int"}
    output_schema: dict                           # 出参契约
    executor: Callable[[dict], dict]              # 执行入口

class AgentSkill:
    """行业Skill的统一包装"""
    def __init__(self, spec: SkillSpec, plan_capable: bool = False):
        self.spec = spec
        self.plan_capable = plan_capable

    def run(self, **kwargs) -> dict:
        validate(kwargs, self.spec.input_schema)   # 参数校验
        result = self.spec.executor(kwargs)
        validate(result, self.spec.output_schema)  # 结果校验
        return result

description字段至关重要------它是Planner判断"该调用哪个Skill"的依据;输入输出schema则让Skill可以像乐高积木一样被组合,同时为失败检测提供了契约基础。

四、实录一:用ReAct构建原子Skill

以"查询供应商应付账款余额"为例。这是一个原子操作,内部不需要复杂规划,但需要根据用户提供的模糊信息(供应商名称、简称、或编号)做判断。这正适合ReAct:

python 复制代码
def make_balance_query_skill(db) -> AgentSkill:
    def executor(args: dict) -> dict:
        target = args["supplier"]

        def lookup_supplier(name: str) -> str:
            rows = db.query("SELECT id, name, code FROM supplier WHERE name LIKE ?", f"%{name}%")
            return str(rows) if rows else "未找到匹配供应商"

        def lookup_balance(supplier_id: int) -> str:
            row = db.query_one(
                "SELECT SUM(amount) AS bal FROM ap_balance WHERE supplier_id = ?",
                supplier_id)
            return f"应付余额:{row['bal']:.2f}元" if row else "无余额记录"

        tools = {"lookup_supplier": lookup_supplier, "lookup_balance": lookup_balance}
        return {"balance": react_loop(f"查询供应商[{target}]的应付余额", tools)}
    ...

ReAct在这里的价值是:模型不知道供应商编号,但可以自行选择"先按名称模糊查询、再按ID查余额"的工具链,甚至能处理"重名供应商需要用户确认"的边界。把这个能力封装进Skill后,上层完全不需要关心它的内部挣扎。

五、实录二:用Plan-and-Execute编排复合Skill

单点查询远不够,业务要的是"月度应付对账报告"。这个Skill内部需要拆解为:拉取供应商清单 → 逐家查询余额 → 与上月快照比对 → 生成差异分析 → 汇总成报告。步骤间有明确的数据依赖,这正是Planner的舞台:

python 复制代码
def make_monthly_reconcile_skill(db, report_writer) -> AgentSkill:
    def executor(args: dict) -> dict:
        month = args["month"]

        def plan_and_run(plan: list[dict], ctx: dict) -> dict:
            for step in plan:                       # 顺序执行计划
                fn = STEP_REGISTRY[step["op"]]      # 从注册表取执行函数
                step["result"] = fn(month=month, ctx=ctx, **step.get("params", {}))
                ctx[step["id"]] = step["result"]
            return ctx

        # Planner:LLM根据任务与步骤注册表生成计划
        plan = planner.generate(
            task=f"生成{month}应付对账报告",
            available_ops=list(STEP_REGISTRY.keys()),
        )
        ctx = plan_and_run(plan, {})
        return {"report": report_writer.render(month, ctx)}

注册表模式是关键设计:Planner不直接写代码,而是从预定义的STEP_REGISTRY里挑选已实现、已验证的操作。这既限制了LLM的自由度防止幻觉性步骤,又保留了编排的灵活性:

python 复制代码
STEP_REGISTRY = {
    "list_suppliers": list_suppliers,
    "query_balance":  query_balance,
    "diff_with_last": diff_with_last,
    "risk_analysis":  risk_analysis,
    "render_report":  render_report,
}

六、实录三:反馈驱动的执行修订(两框架的缝合点)

纯顺序执行在理想数据下没问题,但现实数据总有脏数据:某供应商上月余额为空、某笔调整单日期越界......此时Executor必须在步骤内用ReAct自救,自救失败则把异常回抛给Planner让其修订计划。我在两者之间加了一个"异常走廊":

python 复制代码
class ReconcileExecutor:
    def execute_step(self, step, ctx):
        try:
            return self.run_with_react(step, ctx)        # 步骤内部:ReAct细粒度执行
        except StepHardError as e:
            repair = self.planner.repair_plan(e, step)   # 步骤失败:Planner修订
            if repair is None:
                raise
            return self.execute_step(repair, ctx)        # 修订后重试

    def run_with_react(self, step, ctx):
        react_loop(
            f"{step['op']}:{step['params']}",
            tools=STEP_TOOLS[step["op"]],                # 每个操作有自己的工具集
            max_steps=5,
        )

实际运行中,这个机制处理了相当比例的真实异常------例如"对账差异超阈值"这种数据异常,Planner会追加"人工复核"步骤而不是硬算;而"上游接口超时"这种环境异常,则直接标记该步骤为阻塞并降级输出。

七、工程化经验:让Skill真正能交付

框架选对了,决定成败的往往是工程细节。以下是四个月实战踩坑后的沉淀:

1. 提示词中的"工具契约"要显式化。 Planner和Executor共享一份统一的工具/操作定义,包含参数、返回值、错误语义。我用JSON Schema生成,避免模型从自由文本里猜测参数类型。

2. 失败要分级。 我把错误分成RecoverableError(重试可解)、StepHardError(需修订计划)、FatalError(终止并转人工)。分级让异常走廊的决策变成查表而不是另一个LLM调用,既省钱又稳定。

3. 全程可观测。 每个Thought/Action/Observation、每版计划、每次修订都写入结构化日志(JSON Lines),配有trace_id贯穿全链路。行业场景下,"为什么得出这个结论"和结论本身同等重要------审计要求它,调试需要它,用户信任也依赖它。

4. 成本与延迟要预设上限。 max_steps、每步Token预算、Planner重规划次数全部硬编码上限;能并行执行的步骤(如逐家供应商查余额)用并发池并行,实测报告类任务耗时下降约60%。

5. 用真实失败样例做回归。 我把历史上每个导致链路崩溃的案例固化为测试用例,每次修改Skill定义或提示词后全量跑一遍,确保"修好A不弄坏B"。这是这套体系从"demo"走向"可用"的临门一脚。

八、总结

回看这段实践,我的核心收获是:不要在"用哪个框架"上站队,而是让框架服务于任务的形状。ReAct提供动态纠错的能力,Plan-and-Execute提供全局可控的结构,Skill则是对两者的封装与沉淀------它把一次性的智能体编排,变成行业里可复用、可测试、可审计的工程资产。

当前这套体系已支撑起对账、合规初筛、报告生成三类业务,日均处理数千个任务,人工介入率从初版的90%下降到约15%。下一步,我计划引入记忆机制让Skill记住历史偏好,以及用离线评测集量化每次提示词改动的效果。

技术没有银弹,但把"任务拆解"从模型的隐式行为,变成显式、可编排、可观测的工程结构------这条路,已被证明是智能体走向行业生产环境的可行路径。

相关推荐
麻雀飞吧44 分钟前
近期量化工具怎么选,先看你卡在哪一环
人工智能·python
通信仿真爱好者44 分钟前
第【109】期--基于神经网络的OFDM峰均功率比降低方法--python完整代码
python·神经网络·ofdm·限幅滤波·峰均功率比·papr降低
阿图灵1 小时前
OpenCV 图像特征与匹配:SIFT 特征检测与 BFMatcher 暴力匹配
图像处理·人工智能·python·opencv·计算机视觉·sift
daols881 小时前
vue 表格vxe-table 实现紧凑型表格的方式
前端·javascript·vue.js
王志来137944730081 小时前
工控服务器机箱选型决策要素解析:匀天以“快全准省”构建价值坐标
运维·服务器·python·devops
恋猫de小郭1 小时前
Flutter 多窗口支持类型和 API 介绍
android·前端·flutter
必须会一定会1 小时前
AI 前端项目验收:Playwright 截图、响应式视口、控制台错误与交互回归
前端·人工智能·gpt·交互·ai编程
Java搬码工1 小时前
VUE3使用教程
前端·javascript·vue.js