从零构建AI助手驱动的智能工作流系统,这句话我一开始也是当口号看的。直到我连续被凌晨三点的告警电话吵醒两次,才下定决心把手里那套“定时脚本+if-else大法”推翻重来。这个过程中最顺手的工具,是一个相对小众的全栈自动化框架——OpenClaw。它没有炫酷的可视化界面,也不像某些大而全的Agent平台那样什么都管,但它把两件事做到了让我满意的程度:让大模型真正在可控边界内做决策,同时保留传统脚本的确定性执行能力。
这篇文字就是一份完整实战记录。我会从为什么放弃规则脚本讲起,介绍OpenClaw的架构定位,再给出可以直接照着抄的搭建步骤、一个真实的数据采集周报案例,以及我在生产环境里踩过的四个大坑。如果你已经被重复劳动折磨过,或者刚好想把AI能力从“聊天玩具”升级成“能干活的系统”,这篇内容应该能省下你不少弯路。
1. 为什么我放弃“规则脚本”,转向AI驱动的工作流编排
1.1 一段真实的“脚本事故”
先说个亲身经历。我之前维护过一个数据采集系统,核心逻辑很简单:定时请求上游接口,把返回的JSON清洗后入库,再触发下游报表生成。听上去人畜无害,但实际运行了大半年之后,我开始每隔几周就收到一次凌晨的告警。
印象最深的一次:上游接口把字段从amount改名成了total_amount,接口文档也更新了,但我的解析脚本不会自己跟着更新。正则和字典取值全部失配,脚本直接静默跳过当天数据,下游报表缺了一整天,最后是业务同学发现数字对不上,半夜电话打到我这里。修复倒是五分钟的事,加一行兼容逻辑就行。但我知道,这种问题会像打地鼠一样反复出现:字段改名、接口超时、数据结构嵌套层级调整、服务端悄悄改了分页参数……任何一个微小的变化都足以让精心设计的脚本变成一堆废代码。
那段时间我反复问自己一个问题:我要的到底是“能跑的脚本”,还是“能应对不确定性的工作流”?答案显然是后者。
1.2 规则脚本的语义鸿沟
传统定时脚本和AI驱动的工作流,核心差别不是“用了大模型”,而是“谁来处理异常分支”。
我写过很多年脚本,很清楚它的本质:你提前把所有可能性写成if-else和异常捕获,程序严格按分支执行。问题在于,现实世界的业务变化,很少会按照你预埋的if-else变化。今天你处理了字段缺失,明天它可能返回一个新的code表示“功能下线”;今天你处理了超时重试,明天它可能返回200但body为空。写规则脚本的人不是在写业务逻辑,而是在写对未来的预测——而这个预测注定是不完整的。
传统的RPA自动化也有类似的尴尬。它能精确模拟鼠标点击和键盘输入,但遇到页面布局调整、验证码变异、弹窗文案变化,整个流程就会卡死。它的问题不是“执行不了”,而是“不理解”。
AI驱动的工作流把顺序调过来了:大模型负责理解当前状态并选择下一步操作,代码负责把选定的操作稳定执行掉。也就是说,异常分支不再是代码里逐条枚举的,而是模型在运行时根据实际输入动态判断出来的。这不代表脚本没有价值,脚本价值变成了“精确的工具”,而模型变成了“会选工具的调度者”。
1.3 我给OpenClaw的一句话定义
OpenClaw是我在横向对比了多个开源自动化框架之后选中的一套方案。如果让我用一句话向同事介绍它,我会说:OpenClaw是一个“模型做决策、代码做执行、工作流图做控制”的全栈自动化运行时。
它既不是RPA那种脚本录制模拟,也不是纯粹的Agent框架。它更像一个带状态机的编排平台:你可以定义一条工作流,工作流里的每个节点可能是“调用一个函数”“请求一次大模型”“暂停等待人工确认”,节点之间可以传参、可以跳转、可以循环。而大模型被嵌入到决策节点中,在它该发挥作用的地方发挥作用,在它不需要出现的地方完全不出现。
这样做的好处非常直接:风险可控。你不需要让大模型去操作键盘,不需要让它在一个巨大的权限空间中自由发挥,它只能调用你在工具注册表里明确暴露的那些函数。给它多大的权限,它就只能在多大范围内折腾。
1.4 这篇实战记录适合谁
如果你是想尝试AI自动化的后端开发、运维工程师、数据分析师,或者正在做内部效率工具的产品同学,这篇文章应该是合适的。我默认你已经会写一点Python,了解基本的HTTP请求、JSON和定时任务概念;不需要你有大模型训练经验,因为整条链路里的“AI”部分,大部分时候是通过提示词和API调用完成的。
2. OpenClaw的定位:不是RPA,也不是Agent框架,而是两者的粘合层
2.1 RPA和Agent框架各自的尴尬
市面上做自动化的产品方向通常分两派。
一派是RPA,核心思路是“模拟人操作电脑”。它最擅长处理那些没有API、只能通过界面操作的老系统。但它有一个天然天花板:它没有语义理解能力。页面上的按钮从“确定”改成“确认”,RPA流程就要改配置;接口调整了字段顺序,鼠标点击脚本无从感知。你维护的不是流程,而是对UI像素级别的刻舟求剑。
另一派是Agent框架,核心思路是“让大模型自主规划并调用工具”。这类方案上限很高,但落地时经常让人头疼:模型可能在一个简单任务上设计出过长的步骤;工具调用失败后,模型会反复尝试同一个错误参数;自由度太高时,甚至会出现“为了完成目标而绕过约束”的行为。我在调研阶段就见过不少演示很惊艳、一上真实任务就失控的案例。
OpenClaw抓住了中间地带:它承认“大模型很聪明,但不应该让它从头到尾自由发挥”,也承认“传统脚本很可靠,但它需要一个会随机应变的调度者”。于是它把流程结构用工作流图固化下来,只在特定决策点引入模型判断。这样既保留了执行层的确定性,又在判断层获得了灵活性。
2.2 五个核心组件
用了一套之后,我认为理解OpenClaw只需要抓住五个组件:
| 组件 | 作用 | 我的理解 |
|---|---|---|
| 工作流运行时 | 维护流程状态,控制节点执行顺序 | 相当于流程的“操作系统” |
| 工具注册表 | 暴露可被模型调用的函数及参数定义 | 给模型一个“工具箱” |
| 模型适配层 | 统一接入不同大模型的推理接口 | 屏蔽模型差异,随时可换模型 |
| 上下文管理器 | 记录对话历史、中间状态、临时数据 | 防止AI“失忆” |
| 人工确认节点 | 暂停流程,等待人工审批或指令 | 给高风险操作留一个“刹车” |
这五个组件不是孤立的。工作流运行时每走到一个决策节点,就会把当前上下文交给模型适配层;模型的输出被解析成结构化的工具调用参数,再回到工作流运行时去触发工具注册表里的具体函数;如果节点标记为需要人工确认,则流程挂起,直到外部指令到达。
2.3 最小工作流长什么样
OpenClaw支持用Python或YAML定义工作流。我习惯用YAML定义流程拓扑,用Python写具体的工具函数。下面是一个最小示例,包含两个节点:一个“让模型判断”的决策节点,一个“执行工具”的动作节点。
workflow: id: demo_weather start: decide_action nodes: - id: decide_action type: llm model: general-llm system_prompt: | 你需要判断用户意图,并决定是否调用天气查询工具。 如果用户询问天气,输出工具调用;否则输出拒绝。 input: "${user_input}" output: next_node - id: call_tool type: tool tool: query_weather params: city: "${decide_action.city}" output: final_answer这个文件表达的意思很简单:先让模型做决策,模型决定调用query_weather并给出city参数,然后执行节点调用工具。如果你不想让模型做那么多判断,也可以直接写一个一次性的工具执行链——模型甚至不会出现。这种“按需使用AI”的灵活性,是我最终选择它的原因之一。
3. 从零搭建:三小时跑通第一个AI驱动工作流
3.1 安装与项目骨架
OpenClaw的安装非常简单,基于Python环境,一条命令即可:
pip install openclaw装完之后,创建一个项目目录,建议按下面的结构组织代码:
my_workflow/ ├── workflows/ # YAML工作流定义 │ └── demo_weather.yaml ├── tools/ # 工具函数,按业务域拆分 │ ├── __init__.py │ └── weather.py ├── config/ │ ├── models.yaml # 模型接入配置 │ └── tools.yaml # 工具权限与别名声明 └── main.py # 启动脚本目录结构别小看。早期我图省事把所有工具函数写在一个文件里,结果工具数量一多,注册表变得乱七八糟,模型误调用的概率也随之上升。按业务域拆分工具模块,是OpenClaw项目能长期维护的重要前提。
3.2 注册第一个工具:天气查询
工具函数的注册方式类似装饰器模式。下面这段代码定义了一个“查询天气”的工具:
# tools/weather.py from openclaw import tool @tool.register( name="query_weather", description="查询指定城市的当前天气", params={ "city": { "type": "string", "description": "城市名称,如北京、上海、广州", "required": True }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位,默认摄氏度", "required": False } } ) def query_weather(city: str, unit: str = "celsius"): # 这里替换成你真实可用的天气接口 url = "https://weather-api.example.com/current" resp = http_get(url, params={"city": city, "unit": unit}) return resp.json()请注意params这个参数。它不只是文档,它直接定义了模型可以填什么、必须填什么、取值范围是什么。如果这个schema写得很含糊,模型就会自由发挥。我见过一个工具把params里城市字段描述写成“传入城市名”,模型就会时不时传一个英文名加一个备注,导致接口报错。把描述写清楚,是减少模型幻觉参数最有效的投入之一。
3.3 定义一个“决策+执行”的简单工作流
工具注册好之后,我们来定义工作流。在workflows/demo_weather.yaml里写:
workflow: id: demo_weather description: "用户输入问题,AI助手决定是否调用天气查询" entry: decide nodes: - id: decide type: llm model: general-llm system_prompt: | 你是天气预报助手。你能使用的工具只有 query_weather。 当用户明确询问某个城市的天气时,调用该工具; 否则请直接礼貌说明无法处理。 messages: - role: user content: "${input_text}" tools: - query_weather output_field: action - id: execute type: tool tool: "${decide.action.tool_name}" params: "${decide.action.tool_params}" output_field: weather_result - id: reply type: llm model: general-llm system_prompt: | 你是天气预报助手,请根据工具返回的天气数据,生成一段友好的中文回复。 messages: - role: user content: "${weather_result}" output_field: final_reply这里的关键写法是:decide节点输出action,里面包含tool_name和tool_params;execute节点再根据action去动态选择工具。这种“让模型先决定调用哪个工具、再让运行时去执行”的模式,是OpenClaw工作流中最常见的主干逻辑。
3.4 第一次运行:理解模型调用轨迹
在main.py里写一个简单入口:
from openclaw import Workflow workflow = Workflow.from_yaml("workflows/demo_weather.yaml") result = workflow.run(input_text="北京今天冷吗?") print(result["final_reply"])运行之后,你会看到类似这样的日志输出:
[node decide] model=general-llm output={"tool_name":"query_weather","tool_params":{"city":"北京","unit":"celsius"}} [node execute] tool=query_weather result={"temperature": 3, "condition": "晴"} [node reply] model=general-llm output="北京今天气温3度,晴,体感偏冷,建议穿厚外套出门。"第一次完整看到“模型思考→工具执行→模型总结”的这一整套链路时,我还是很兴奋的。从日志里你可以很直观地看到模型的决策质量:它有没有正确提取城市名、有没有给到合法参数、工具返回后有没有正确总结。这套日志就是后续调试和优化的基础。
4. 真实案例:数据采集→分析→生成周报→推送
4.1 需求拆解
光会查询天气还不够。真正让我对OpenClaw产生信心的是一个内部需求:每周整理三个数据源的运营数据,生成一份Markdown周报,再推送到团队群。
原先这件事靠我每周一早上花两个小时手动完成:登录后台、导出表格、复制到模板、改格式、粘贴到群里。我其实也试过写脚本,但数据源的字段经常微调,脚本每次都要改,改完还要跑一遍检查有没有列错位,维护起来比手动还累。
用OpenClaw做这件事时,我没有让模型“自行完成任务”。相反,我把任务拆成了四个明确的工具,让AI助手按照固定流程组装。
4.2 四个工具的schema设计
| 工具 | 功能 | 核心参数 | 风险等级 |
|---|---|---|---|
fetch_source_data | 拉取指定数据源最新数据 | source_id、date_range | 低 |
transform_data | 按业务口径聚合计算 | rule_name、columns | 低 |
generate_report | 把数据渲染成Markdown报表 | template_name、extra_notes | 低 |
send_webhook | 推送消息到群聊 | payload、channel | 中 |
每个工具我都写清楚了参数和权限。fetch_source_data只暴露source_id枚举值,模型不能自己发明数据源名称;send_webhook限定只允许推送到某个内部渠道,不允许指定任意URL。这样即使模型理解有偏差,最坏的结果也只是“拉错了数据”或者“推送到了默认群”,而不会出现越权操作。
4.3 核心提示词设计
工作流的起点是一段system prompt。我写的是:
你是一名运营数据分析助手。你的目标是完成周报生成任务。 步骤约束: 1. 必须依次调用 fetch_source_data 获取三个数据源的数据; 2. 如果某个数据源返回结构异常,可以重试一次;仍然异常则跳过该源,并在最终报告中明确标注缺失; 3. 使用 transform_data 汇总核心指标; 4. 使用 generate_report 生成Markdown周报; 5. 在推送前,先对报告做一次完整性校验,确认缺失数据已标注; 6. 最终调用 send_webhook 推送到 default 频道。 禁止事项: - 禁止编造任何数据; - 禁止跳过 fetch_source_data 直接生成报告; - 禁止修改模板中的固定格式字段。这段提示词的核心作用不是“教模型做事”,而是“告诉模型哪些边界不能跨越”。我观察到,只要把步骤约束写成显式的执行顺序,模型的工具调用序列就会稳定很多。如果把目标写得过于开放,模型可能今天按顺序执行,明天突发奇想先生成再拉数据,流程就会乱。
4.4 一次真实运行的日志观察
实际运行时,我截取了一段比较有意思的日志:
[node analyze] model=general-llm 步骤1:fetch_source_data({"source_id":"source_alpha","date_range":"2025-02-03~2025-02-09"}) 结果:正常,返回12条记录 步骤2:fetch_source_data({"source_id":"source_beta","date_range":"2025-02-03~2025-02-09"}) 结果:返回结构异常,缺少expected_field 重试1次,结果仍异常 决策:跳过source_beta,标记缺失 [node analyze] model=general-llm 步骤3:transform_data(...) 步骤4:generate_report(...) 步骤5:完整性校验,发现缺失已标注 步骤6:send_webhook({"channel":"default","payload":"..."})模型在遇到source_beta字段缺失时的处理方式,正是我选择AI驱动工作流的理由。如果换成传统脚本,我必须预先写好“如果source_beta少了expected_field就跳过并标记”的逻辑;而AI驱动版本在运行时自行判断,并且严格按照提示词里的约束做了重试与降级。任务最终顺利生成周报并推送,全程耗时不到三分钟。
4.5 这个案例的收益与成本
落到实际收益,这个工作流现在每周自动运行一次。原本两小时的重复劳动被压缩到接近零人工。我只需要每周一瞄一眼运行日志,偶尔抽查报告质量。投入的成本主要是模型调用的token费用,因为周报数据量不大,每次运行成本折合下来可以忽略。
如果要把这个经验总结成一句话:不要指望AI助手“顿悟”你复杂的业务流程,而是把流程拆成稳定的工具和明确的步骤约束,让AI只在需要判断的地方发挥作用。这个思路适用于绝大多数自动化场景。
5. 全栈自动化最容易翻车的四个坑
5.1 坑一:模型幻觉参数,导致工具执行了脏操作
最早我把一个“批量删除测试数据”的工具开放给模型试运行。修改配置时,本意是删除指定日期之前的数据,模型在生成参数时把日期格式传成了"2025-02-30"这样的非法值,更可怕的是,它给confirm字段自动填了"true"。
还好我提前设了dry-run模式,工具实际没有执行删除,只返回了“将要删除的数据条数”,我一看数字不对,赶紧排查才发现问题。从那以后,我给自己立了一条规矩:所有“破坏性”工具,必须显式要求模型提供confirm_reason字段,且字段值必须由用户在人工确认节点中二次确认,不能由模型自己确认。
@tool.register( name="delete_records", params={ "record_ids": {"type": "array", "items": {"type": "string"}, "required": True}, "confirm_reason": {"type": "string", "required": True} } ) def delete_records(record_ids, confirm_reason): # 此工具默认只在人工确认节点通过后才真正执行 return {"status": "pending_approval"}另外,工具参数schema里的枚举值能限多死就限多死。所有取值能枚举的,都不要让模型自由发挥。自由发挥的空间越小,幻觉带来的后果越轻。
5.2 坑二:上下文窗口漂移,AI做到一半忘了目标
长工作流的持续性问题,比我想象中严重。有一个任务涉及十来步工具调用,前几步模型还很专注,到第五步之后开始“缩小任务范围”,只处理它当前看到的一部分数据,完全忘记了最初的全局目标。
这是上下文窗口太长导致的典型问题。尤其当工具返回的结果很大,历史记录堆积得很长时,模型对早期指令的注意力会被稀释。
我的解法有两个。第一,在每个工具调用节点前,动态注入“当前进度摘要”,用一到两句话重述全局目标。第二,核心字段不依赖模型从长上下文中提取,而是在节点之间显式传递。比如全局要处理的数据源列表,在执行第一步时就写入工作流的全局变量,后续步骤直接引用,避免模型“再次回忆”。
5.3 坑三:幂等性缺失,重试带来重复副作用
网络超时是这个领域最常见的故障。有一次工作流调用外部接口超时,OpenClaw自动重试,结果前面的数据已经成功写入,重试又执行了一次写操作,最终生成了重复记录,还连累下游报表数字翻倍。
排查之后,我给所有写操作工具加入了幂等键设计:每次工作流执行会生成一个execution_id,所有写入操作都带上这个ID,目标系统记录这个ID,重复请求直接返回“已处理”,不重复落库。同时调整了重试策略:读操作可以自动重试,写操作重试前必须检查执行状态。
这个教训也提醒我:工作流编排工具的“自动重试”是一把双刃剑。它对读操作是友军,对写操作是灾难。任何在生产环境跑的自动化,都不能忽略幂等设计。
5.4 坑四:并发与锁——AI助手同时操作多个工作区
我的项目后期引入了多工作流并发运行。某天两个实例同时更新同一份配置,后写的人把前一个人的变更覆盖了,现场一片混乱。问题在于我完全没有做资源隔离。
现在我的做法是:不同场景使用独立的工作区目录和数据库连接;需要在多个工作流间共享的数据,通过一个带版本号的中间表协调;同一类任务,宁可串行排队,也不要允许两个实例同时奔着同一个状态去。说白了,AI助手可以并发思考,但对底层共享资源的并发写,还是得靠传统手段兜底。
锁的顺序:先取版本号,再比较版本号,最后写回并递增版本号。 如果发现版本号已被其他实例更新,当前实例直接放弃本次写操作并重读数据。这套方案执行后,并发冲突再也没有出现过。
5.5 如何把故障当成“演练”
踩过这些坑之后,我养成了一个习惯:定期做故障注入测试。断掉外部接口的连通性、让接口返回401、随机删除响应中的某个字段、给模型一个无法完成的任务,然后观察工作流如何降级、如何报错、是否卡死。
这些演练成本不高,但能暴露很多问题。有一次演练就发现,模型在接口返回401时,没有停下来报告异常,而是反复猜测其他参数尝试调用,白白浪费了很多token。后来我在提示词里明确要求:“遇到认证失败或权限错误,立即停止并报告,不要尝试修改参数重试。”这比任何重试策略都有效。
6. 进阶玩法:多Agent协作与人机确认机制
6.1 把“一个助手”升级成“一支团队”
当任务复杂到一定程度,单个助手的提示词会变得臃肿不堪。我通常会把一条大而全的工作流拆成多个角色明确的Agent,每个Agent拥有独立的提示词和工具权限。
举个例子,“数据采集Agent”只负责拉取原始数据,它看不到推送工具;“分析Agent”只接触清洗后的数据集和规则;“审核Agent”专门检查报告完整性,并有权决定是否退回让上游Agent重做;“推送Agent”负责最后的消息发送。
Agent之间通过工作流运行时传递消息,比如“采集Agent完成”之后,工作流自动把数据交给“分析Agent”。这样做的好处是:每个Agent的工具面都很窄,模型是在一个受限环境里做决策,风险自然降低。同时,当你需要修改某个环节时,只需要调整对应Agent的提示词或工具权限,不会牵一发动全身。
6.2 人机确认:哪一步应该停下来等人工
AI自动化并不意味着所有决策都要模型拍板。我的原则是:高影响、难回退、涉及真实用户的操作,一律加人工确认节点。
OpenClaw提供了一种wait_for_approval节点。工作流执行到这一步会暂停,把待审批信息发送给指定接收人,只有收到确认指令才会继续向下执行。我通常把这种节点放在三类动作之前:对外发送消息、删除或修改大量数据、金额相关的操作。
实现方式很简单:
- id: approval type: wait_for_approval message: 即将向频道推送周报,请确认数据无误。 timeout_seconds: 3600 next_on_approve: send next_on_reject: abort这个节点看似不起眼,却让我愿意把更多重复工作交给自动化。因为我知道,即使模型判断错了,关键节点上还有一道人工闸门。这套“AI干活、关键步骤主持人确认”的机制,比追求全自动更接近真实生产环境。
6.3 生产环境的可靠性设计
在把工作流正式交给定时任务之前,我还做了三件可靠性建设。
第一,模型降级。主模型偶尔会超时或返回格式错误,我在模型适配层配置了备用模型,一旦主模型调用失败,自动切换到备用模型重试。OpenClaw的模型适配层支持多模型配置,切换对工作流透明。
第二,状态持久化。工作流每一步的中间结果都会写入本地状态存储,这样即使进程崩溃,也可以从最近一次成功节点恢复,而不是整体重跑。这个能力在长耗时工作流中尤其重要。
第三,结构化日志。所有模型调用和工具执行,都输出包含trace_id的结构化日志。排查问题时,我只需要根据一次运行的唯一ID,把整个链条的日志捞出来,就能清晰地看到每一步的输入输出,效率比翻大海捞针式的普通日志高得多。
6.4 关于“全栈自动化”的边界思考
做完了这些之后,我对“全栈自动化”有了更务实的理解——它不是要把所有事情都交给AI,而是把AI放在一个合适的决策位置,把重复劳动交给代码,把风险把控交给人。
我现在的使用原则有三个:低风险、高重复、弱合规要求的任务,优先自动化;高风险、强合规、需要明确因果责任的场景,保留人工主导;所有自动化任务,无论看起来多可靠,都保留审计日志和随时中止的能力。
技术再强,也不能替业务承担信任责任。一个能随时停下来的自动化系统,比一个什么都想管的自动化系统,走得更远。
最后分享一个小技巧:对OpenClaw工作流里的每一次工具调用,都加上独立的trace_id,并把它和对应的模型决策日志一起落盘。你排查问题时会发现,很多时候模型看起来是“乱来”,实际是因为某个工具返回了脏数据。有日志链在,你就能在五分钟内定位真凶,而不是在“怀疑模型”和“怀疑代码”之间反复横跳。这个习惯,替我节省的时间远远超过了实现它的成本。