OpenClaw 技能编排实战|如何组合多个技能完成复杂任务链
本系列第十八篇:从“单个技能”到“工作流”——让多个 Skill 像乐高一样拼出真正的自动化
欢迎回到 OpenClaw 系列教程。经过前面十七篇的积累,你已经掌握了 Skill 开发、Hook 拦截、Plugin 扩展等核心能力。你的“龙虾”已经拥有了十几项甚至几十项技能。
但一个真实的自动化任务,很少是单个技能能独立完成的。比如“每天早上 9 点抓取行业新闻 → 生成摘要 → 发送到飞书群”——这需要新闻抓取技能、文本摘要技能、消息推送技能三者协同工作。如果你只是分别教会 AI 这三个技能,它并不会自动把它们串起来。
这就是技能编排要解决的问题。
本文将系统讲解 OpenClaw 中技能编排的三种模式(串行、并行、条件分支),并通过两个完整实战案例(新闻监控推送、多源数据报表生成)演示如何将多个技能组合成可靠的工作流。同时,我们会探讨在 Skill 层、Hook 层、Plugin 层分别实现编排的适用场景与最佳实践。
一、技能编排的本质:从“会做”到“会串”
1.1 为什么需要编排?
OpenClaw 的 Agent 本身具备一定的任务拆解能力——当你给出一个复杂指令时,它会尝试分解成多个步骤,并依次调用相应的工具或技能。但这种“动态编排”存在三个局限:
-
不可复现:同样的任务,今天和明天的执行路径可能不同,难以形成稳定的自动化
-
缺乏流程控制:无法精确控制并行执行、条件判断、重试策略
-
跨技能状态难共享:上一个技能的输出如何传递给下一个技能,缺少标准化机制
技能编排的本质,是将特定场景下的任务流程固化、参数化、可复用,让 Agent 不再是“自由发挥”,而是“按图施工”。
1.2 编排的三种基本模式
|
|
|
|
|---|---|---|
| 串行(Sequential) |
|
|
| 并行(Parallel) |
|
|
| 条件分支(Conditional) |
|
|
在实际工作流中,这三种模式往往会嵌套组合。
二、编排实现的三条路径
在 OpenClaw 生态中,实现技能编排有三种不同层级的方式,各有优劣。
|
|
|
|
|
|
|---|---|---|---|---|
| Skill 内编排 |
|
|
|
|
| Hook 编排 |
|
|
|
|
| Plugin 编排 |
|
|
|
|
路径一:Skill 内编排(最轻量)
直接在 SKILL.md 的“使用说明”部分写出步骤顺序,Agent 会按顺序执行。适合步骤不超过 5 个、没有复杂分支的场景。
路径二:Hook 编排(中等复杂度)
在 Hook 中监听特定事件(如定时心跳、消息接收),然后通过 API 调用多个技能。可以加入条件判断、重试逻辑。
路径三:Plugin 编排(企业级)
编写 TypeScript Plugin,注册一个 Workflow 工具,内部实现状态机、并发控制、错误恢复。适合需要长期运行、可观测、可中断恢复的生产级自动化。
下面通过两个实战案例,分别演示 Skill 内编排和 Plugin 编排。
三、实战案例一:新闻监控 + 摘要生成 + 飞书推送(Skill 内编排)
需求:当用户提供一批 RSS 源或关键词时,自动抓取最新文章,对每篇文章生成摘要,最后将摘要汇总发送到飞书群。
3.1 准备基础技能
我们需要三个基础技能(假设已安装或自定义开发):
-
rss_reader:读取 RSS 源,返回文章列表(标题、链接、正文片段) -
smart_summarize:对给定文本生成 200 字以内的摘要(可复用第 15 篇的 summarize 技能) -
feishu_sender:发送 Markdown 消息到飞书群(需提前配置 webhook)
3.2 编写编排 Skill:news_digest
创建 ~/.openclaw/workspace/skills/news_digest/SKILL.md:
markdown
---name: news_digestdescription: Fetch latest news from RSS, summarize each article, and send digest to Feishu.triggers: ["新闻摘要", "日报生成", "news digest"]tools: [rss_reader, smart_summarize, feishu_sender]---# News Digest Workflow当用户要求生成新闻摘要时,严格按照以下步骤执行。## 输入参数用户需提供:- RSS 源列表(URL 数组)或 搜索关键词(将自动转换为 RSS)- 目标飞书群 webhook(若未提供则使用默认)## 执行步骤### 步骤 1:抓取文章调用 `rss_reader` 技能,参数为 RSS 源列表。等待返回结果,格式为 `[{title, link, content_snippet}, ...]`。若返回为空,输出“暂无新文章”并终止。### 步骤 2:逐篇生成摘要对步骤 1 返回的每一篇文章,依次调用 `smart_summarize` 技能,输入为 `content_snippet`。每篇文章的摘要结果保存到临时变量 `digest_items` 中。### 步骤 3:格式化摘要消息将 `digest_items` 格式化为飞书支持的 Markdown 格式:```markdown# 📰 今日新闻摘要 (YYYY-MM-DD)## 1. [标题1](链接1)摘要内容...## 2. [标题2](链接2)摘要内容...步骤 4:发送到飞书调用 feishu_sender 技能,参数为上一步生成的 Markdown 文本。发送成功后,回复用户“日报已发送至飞书群”。错误处理若某篇文章摘要生成失败(超时或返回空),跳过该篇,继续下一篇若所有文章都失败,回复用户“摘要生成失败,请检查 RSS 源是否有效”
3.2 使用编排 Skill
用户在聊天中输入: > “帮我生成新闻摘要,RSS 源:https://news.cn/rss/tech, https://ai.news/rss” Agent 读取 SKILL.md,自动按照四个步骤顺序执行,最终将摘要发送到飞书。整个过程无需人工干预。
优点:零代码,创建速度快,易于调整步骤顺序。
局限:不支持并行(串行摘要效率较低),错误恢复机制简单,无法持久化工作流状态。
四、实战案例二:多源数据报表生成(Plugin 编排)
需求:每天凌晨 2 点,从三个数据源(数据库、API、本地 CSV)拉取指标,合并后生成 Excel 报表,再发送到钉钉,同时备份到云存储。
4.1 创建 Plugin 结构
bash
mkdir -p ~/.openclaw/workspace/extensions/daily-reportercd ~/.openclaw/workspace/extensions/daily-reporternpm init -ynpm install @openclaw/plugin-sdk csv-parse axios xlsx
创建 openclaw.plugin.json:
json
{"id": "daily-reporter","name": "Daily Report Orchestrator","version": "1.0.0","description": "Orchestrate multi-source data collection, report generation, and multi-channel delivery.","permissions": ["read:files", "write:files", "http:send", "schedule:create"]}
4.2 实现编排器:使用状态机
我们需要三个基础技能(假设已安装或自定义开发):
创建 index.ts:
typescript
import { definePluginEntry } from "@openclaw/plugin-sdk";import { schedule } from "node-cron";import fs from "fs/promises";import path from "path";interface WorkflowState {step: "init" | "fetch_db" | "fetch_api" | "fetch_csv" | "merge" | "generate_report" | "send" | "backup";data: any;errors: string[];startTime: Date;}class DailyReporter {private state: WorkflowState;private readonly workspace = process.env.OPENCLAW_WORKSPACE || "~/.openclaw/workspace";constructor() {this.state = {step: "init",data: {},errors: [],startTime: new Date()};}async run() {console.log("[DailyReporter] 工作流开始");await this.fetchDatabaseMetrics();await this.fetchApiMetrics();await this.fetchCsvMetrics();await this.mergeData();await this.generateExcelReport();await this.sendToDingtalk();await this.backupToCloud();console.log("[DailyReporter] 工作流完成", { duration: Date.now() - this.state.startTime.getTime() });}private async fetchDatabaseMetrics() {this.state.step = "fetch_db";// 示例:通过 exec 工具调用 SQL 查询(实际应使用 Plugin 提供的数据库客户端)const result = await this.runTool("exec", { command: "mysql -e 'SELECT ...' -B -N" });this.state.data.dbMetrics = JSON.parse(result.stdout);}private async fetchApiMetrics() {this.state.step = "fetch_api";const response = await fetch("https://api.example.com/metrics");this.state.data.apiMetrics = await response.json();}private async fetchCsvMetrics() {this.state.step = "fetch_csv";const csvContent = await fs.readFile(path.join(this.workspace, "input/metrics.csv"), "utf-8");// 使用 csv-parse 解析this.state.data.csvMetrics = this.parseCSV(csvContent);}private async mergeData() {this.state.step = "merge";// 合并三个数据源,按时间戳对齐this.state.data.merged = { ...this.state.data.dbMetrics, ...this.state.data.apiMetrics, ...this.state.data.csvMetrics };}private async generateExcelReport() {this.state.step = "generate_report";const XLSX = require("xlsx");const workbook = XLSX.utils.book_new();const sheet = XLSX.utils.json_to_sheet([this.state.data.merged]);XLSX.utils.book_append_sheet(workbook, sheet, "Daily Report");const buffer = XLSX.write(workbook, { type: "buffer", bookType: "xlsx" });const outputPath = path.join(this.workspace, `reports/report_{this.state.data.reportPath})`}};await fetch(webhook, { method: "POST", body: JSON.stringify(message), headers: { "Content-Type": "application/json" } });}private async backupToCloud() {this.state.step = "backup";// 示例:上传到 OSS// await uploadFile(this.state.data.reportPath);}private async runTool(toolName: string, params: any) {// 通过 Plugin API 调用 OpenClaw 内置工具// 实际实现需使用 api.callTool()console.log(`调用工具 {total}`。
Agent 在执行时会自动维护内存变量。
Plugin 编排示例:使用工作区文件
typescript
// 将中间结果写入临时 JSONawait fs.writeFile(path.join(workspace, ".cache/step1.json"), JSON.stringify(data));// 后续步骤读取const data = JSON.parse(await fs.readFile(path.join(workspace, ".cache/step1.json"), "utf-8"));
这种方式适合数据量大、需要断点续传的场景。
六、编排中的错误处理与重试
可靠的工作流必须具备错误处理机制。以下是三种层级下的处理策略:
|
|
|
|
|---|---|---|
| Skill 内 |
|
|
| Hook |
|
|
| Plugin |
p-retry) |
|
Skill 内编排的错误处理示例:
markdown
## 错误处理- 若步骤 1(抓取 RSS)超时,重试最多 2 次,间隔 5 秒- 若仍失败,跳过该 RSS 源,继续处理下一个- 若所有 RSS 源均失败,回复用户“暂时无法获取新闻,请稍后重试”
Plugin 编排中使用 p-retry:
typescript
import pRetry from 'p-retry';async function fetchWithRetry(url: string) {return pRetry(async () => {const res = await fetch(url);if (!res.ok) throw new Error(`HTTP ${res.status}`);return res.json();}, { retries: 3, factor: 2, minTimeout: 1000 });}
七、编排性能优化:并行执行
对于相互独立的步骤,可以并行执行以缩短总耗时。Skill 内编排较难表达并行(Agent 默认串行思考),但可以通过以下方式实现:
方法一:在 Skill 中用列表语法提示并行
markdown
### 步骤 1:并行抓取三个数据源同时执行以下三个操作:- 调用 `rss_reader` 抓取源 A- 调用 `rss_reader` 抓取源 B- 调用 `rss_reader` 抓取源 C等待所有操作完成后,合并结果。
部分 Agent 模型能理解这种并行指令,但不是百分之百可靠。
方法二:Plugin 中使用 Promise.all
typescript
const [dataA, dataB, dataC] = await Promise.all([fetchSourceA(),fetchSourceB(),fetchSourceC()]);
这是最可靠的方式。
八、最佳实践总结
-
从简单开始:优先用 Skill 内编排,直到步骤超过 5 个或需要条件分支时再考虑 Hook/Plugin
-
显式定义输入输出:每个技能明确声明需要什么输入、产生什么输出,便于编排时连接
-
为编排单独创建 Skill:不要将编排逻辑散落在多个 Skill 中,而是创建一个“工作流技能”,专门负责调用其他技能
-
记录执行日志:在工作流的关键节点打印日志,便于排查问题
-
设置超时和熔断:避免某个技能卡住导致整个工作流挂起,可使用
Promise.race设置超时 -
幂等性设计:工作流应能安全地重复执行,避免重复发送消息或重复写入数据
九、常见问题与排障
Q1:Agent 不按我 Skill 中写的顺序执行
检查 SKILL.md 中的步骤描述是否清晰,使用“步骤 1”“步骤 2”这样的编号。也可在步骤前加上“必须”“严格按照顺序”等强约束词。
Q2:如何传递复杂数据结构(如数组、对象)
在 Skill 内编排中,用自然语言描述“将结果保存为 JSON 字符串”,后续步骤再解析。在 Plugin 中直接使用 TypeScript 对象传递即可。
Q3:工作流执行到一半崩溃,如何恢复?
Plugin 编排中可将工作流状态持久化到数据库,并在启动时检查是否有未完成的工作流,实现断点续传。
Q4:并行执行时如何限制并发数?
使用 p-limit 库控制并发度,避免资源耗尽。
typescript
import pLimit from 'p-limit';const limit = pLimit(3);const results = await Promise.all(urls.map(url => limit(() => fetch(url))));
十、下一步做什么?
技能编排是将 OpenClaw 从“玩具”变成“生产力工具”的关键一步。掌握编排后,你可以构建高度自动化的业务场景。
-
第 19 篇:OpenClaw 微信接入教程|ClawBot 官方插件 + 企业微信自建应用,两种方案全掌握
-
第 23 篇:多 Agent 协作——让多个 Agent 各自执行编排好的工作流,再汇总结果
-
第 24 篇:定时任务——用 Cron 触发编排好的工作流,实现无人值守
-
第 26 篇:任务编排高级篇——使用 Camunda 或 Temporal 集成外部工作流引擎
💡 最终提醒:编排的价值在于将不确定性转化为确定性。不要试图编排一切——保留一定程度的 Agent 自主决策能力,会让系统更灵活。找到“固定流程”和“智能决策”的平衡点,是设计良好工作流的艺术。
夜雨聆风
