去年底接手了一个产线数字化的活儿,本来以为又是一套常规的“报表+看板”组合,结果做着做着跑偏了——我用一个叫 OpenClaw 的智能体框架,在公司内部搭了一只“产线指挥官”。说得直白一点,它不是一个聊天机器人,而是一个能听懂人话、自己做判断、直接调产线接口干活的 Agent。从“对话”到“操控”,这中间的距离比大多数人想象的要大得多,也正因为如此,这个项目让我踩了不少坑,也攒了不少干货。
这篇文章就把整个项目的来龙去脉、技术选型、实际落地过程和问题排查经验完整写一遍。如果你是做 AI 应用落地、折腾过智能体框架,或者正打算把大模型接到生产系统里,这篇文章应该能帮你避开一些我踩过的坑。
1. 项目整体设计与思路拆解
1.1 为什么选“产线指挥官”这个场景
传统产线的设备控制,基本是 PLC 逻辑加 HMI 触摸屏,操作员按按钮、填表单、看指示灯。系统之间也有上位机,但说白了还是“人找系统”,操作员得记住一堆菜单路径和按钮位置。我们这次想解决的问题很直接:能不能让操作员用自然语言直接下达指令,比如“把3号线的传送带速度降到 0.8 米每秒”或者“点胶机出胶量调大 10%”,由系统自主完成意图识别、指令转换、设备调用和结果反馈。
选择这个场景,还因为它具备几个很适合 Agent 落地的特性:一是设备操作路径相对固定,指令集合有限,不存在真正意义上的开放对话;二是产线控制天然要求结果可追溯,每一步操作都得有日志,这正好可以发挥 Agent 的“过程可解释”能力;三是这个场景的风险足够高,反而逼着我们把安全机制设计到位,而不是停留在 Demo 阶段。
1.2 为什么选 OpenClaw 而不是自己写一套
当时市面上也有不少智能体框架,我也试过几个,但最后留在 OpenClaw 上的原因有三个。
第一,OpenClaw 对工具调用的支持做得比较成熟。控制产线本质上是让大模型学会调用外部 API,OpenClaw 把工具定义、参数校验、调用结果回填整个链路都封装好了,我不需要从零去写函数调用的循环逻辑。第二,它内置了任务规划和多轮记忆管理,操作员说一句话可能需要拆成多个动作,比如“先看一下当前温度,再决定要不要调整”,这种依赖上下文的多步操作,用裸 API 去写会非常痛苦。第三,OpenClaw 的事件机制比较干净,各种系统事件都可以独立订阅,这方便我把它接入产线已有的消息总线,不用改动原有系统太多。
需要说明的是,以下架构和实现细节是基于我当时的实际使用习惯做的合理补充。不同版本的框架 API 可能有变化,但核心思路是可以复用的。
1.3 整体架构长什么样
整个系统分四层,各干各的事。
最上层是交互层,就是操作员面前的对话界面,可以是网页、企业微信机器人,甚至是一个语音助手。这一层只负责把语音或文字转成文本,不做任何意图理解。
第二层是 OpenClaw 智能体核心,负责意图识别、任务规划、工具选择和上下文管理。它拿到的输入是操作员的自然语言加设备状态快照,输出给第三层的是结构化的设备指令。
第三层是产线控制网关,这是我自己写的适配层,也是整个系统里最关键的一环。它接收 Agent 层下发的结构化指令,负责做权限校验、设备互斥检查、指令翻译和熔断保护,然后才真正去调用 PLC、变频器、传感器这些底层设备。
第四层是设备层,就是各种物理设备和它们的接口,包括 OPC-UA 服务器、Modbus TCP 网关、IO 模块等等。这一层跟我们原来的系统完全一样,Agent 只是替换了原来人工操作的位置。
这个架构的核心思想,是把“智能”和“控制”彻底分开。智能体负责思考,但不直接接触设备;网关负责执行,但不需要理解自然语言。中间的接口就是一份严格定义的 JSON 结构,两边各管一头,出了问题也容易定位。
2. 核心细节解析与实操要点
2.1 从自然语言到设备指令的映射逻辑
这是“从对话到操控”最关键的一跳。操作员说“转速有点高,降一点”,这句话里有采样信息(转速有点高)、模糊量级(降一点)、缺少对象。要做映射,第一步是把这句话解析成一个意图模板:目标设备是某变频器,操作类型是调速,参数是负向调整,调整量是模糊量。
我们的做法是给 OpenClaw 配置了一份“领域词典”,把产线常用的设备名、操作动词、量词、方向词全部做了标准化。比如“传送带”映射到设备标识 CONV_L3,“点胶机”映射到 DISP_01,“快一点”映射到 speed_up,“加一点”映射到 delta_mode。让大模型先把口语翻译成标准化的“槽位填充结果”,再交给规则层做最终解释,而不是让大模型直接输出设备指令。
这套做法的好处是让大模型的自由度被约束在一个安全范围内。设备控制不是写文案,不需要创造力,宁可让模型说“我不确定”也不能让它自由发挥。我们给所有工具调用都加了 JSON Schema 校验,参数类型、枚举范围、必填项都卡得很死,模型输出不符合规范就重新生成,最多重试三次,三次都不行就转人工。
2.2 关键参数与模型选择
模型温度直接拉到了 0.1,控制类任务不允许有太多随机性。我也试过用默认温度,结果同一句话隔几分钟问,给出的参数能差出 5% 来,这在产线上是不可接受的。
上下文管理上踩了一个大坑:不能把全部设备状态都塞给模型。产线设备几百台,每个设备的实时状态几千个字节,一次性全塞进去,Token 直接爆炸。后来我改成“按需注入”模式,模型需要了解某台设备的状态时,才通过工具调用去读实时快照,读完后把结果作为工具响应用完即弃,不进入长期记忆。
模型选的是支持超长上下文的版本,但我发现长上下文既是福利也是陷阱。Agent 在往后翻旧记录时,容易把早先的指令误当成当前指令,尤其是两个操作员在同时操作不同设备时,指令串场的问题特别明显。后面我们加了定时器和指令作用域,模型只能看到当前任务相关的对话片段,才把这个问题压下去。
2.3 工具定义的设计原则
OpenClaw 里的工具定义就是一份 JSON Schema,我最初只是照猫画虎把设备接口的参数抄了一遍,结果模型根本不知道怎么用。后来我总结出一个经验:工具定义要写的是“意图”,不是“接口”。
比如“调整设备开关”这个工具,接口层面只有启停两个动作,但操作员真正想表达的是“设备过温了,先停一下”,这时候模型得先把情况判断为“需要执行停机关闭”,再调用工具。如果工具定义里不写清楚触发场景,模型就会在“关闭”和“暂停”之间猜。后来我每个工具的 description 里都写上了典型触发场景和反例,识别准确率才上来。
另外,工具的返回结果也要精心设计。我们返回的不是原始寄存器值,而是经过加工的状态描述加机器可读字段。状态描述给模型看,机器字段给规则引擎用,两边都能理解。
3. 实操过程与核心环节实现
3.1 部署基础环境
我用一台普通工作站当作智能体宿主,系统是 Linux,装上 Docker 和 Python 运行时。OpenClaw 我选了 Docker Compose 的部署方式,把服务拆成 API 网关、任务调度、模型适配器三个容器,日志统一收集到 Elasticsearch,监控用 Prometheus。
关键的一步是要给智能体宿主单独划一个网段,跟产线控制网做一个逻辑隔离。Agent 本身要访问大模型 API,走的还是外网,如果不做隔离,安全问题会很麻烦。实际部署时我用了一台双网卡机器,一张网卡走办公网访问模型服务,一张网卡走内部控制网访问网关接口,中间通过自研的鉴权代理转发,不允许任何从智能体宿主直接访问 PLC 的通道。
3.2 配置 OpenClaw 基础能力
以下是配置的核心参数示例,我用的是当时项目里的简化配置文件:
agent: name: line-commander model: provider: local-gateway model_name: code-sense-v3 temperature: 0.1 max_tokens: 4096 memory: type: sliding-window window_size: 20 summary_threshold: 8 planner: max_steps: 5 require_confirmation: true tools: allowlist: - get_device_status - set_device_speed - set_device_switch - set_device_parameter - retry_pending_task - abort_current_task这里的几个参数值得逐一说一下。temperature 前面讲过,0.1 是控制任务的下限附近,再低会损失语义理解的灵活性。memory 的 sliding-window 设成 20 轮对话,8 轮之后开始做摘要压缩,这是平衡上下文费用和任务连贯性的一个折中值。require_confirmation 必须打开,只要涉及设备状态变更的操作,模型先给出指令草案,由网关二次确认后才能真正执行。
工具 allowlist 是白名单模式,只有这个列表范围内的工具能被模型调用,其他一律拒绝。我当时把列表收得比较紧,宁可多写几个专用工具也不提供一个万能工具,这样出了问题好追查。
3.3 产线控制网关的实现
网关是整个系统里我写得最痛苦也最值得的部分。核心功能是把 Agent 的意图指令转换成设备层能理解的协议报文。我贴一段简化后的核心处理逻辑:
class DeviceCommandGateway: def __init__(self, device_registry, permission_checker): self.devices = device_registry self.permission = permission_checker self.lock_table = {} def execute_command(self, cmd: dict) -> dict: device_id = cmd["device_id"] action = cmd["action"] payload = cmd["payload"] if device_id not in self.devices: return {"status": "error", "message": f"未知设备: {device_id}"} if not self.permission.check( operator=cmd.get("operator", "system"), device_id=device_id, action=action ): return {"status": "error", "message": "权限不足"} # 设备级互斥锁,同一台设备不允许同时执行两个写操作 if not self.lock_table[device_id].acquire(blocking=False): return {"status": "error", "message": "设备忙,有其他操作正在执行"} try: result = self.devices[device_id].execute(action, payload) self._audit_log(cmd, result) return {"status": "success", "data": result} finally: self.lock_table[device_id].release() def _audit_log(self, cmd, result): # 审计日志记录操作人、时间、指令内容、设备状态变化 audit_item = { "ts": time.time(), "operator": cmd.get("operator", "system"), "device": cmd["device_id"], "action": cmd["action"], "payload": cmd["payload"], "result": result, } self.audit_queue.put(audit_item)这段代码里最值得强调的是互斥锁那一层。多操作员同时下达指令时,如果不对同一台设备做串行化,各种并发问题都会冒出来。我有一次测试时让两台设备分别控制同一个传送带的前后段速度,结果出现“前段加速后段减速”的奇葩状态,传送带差点绷断。后来在网关上把所有写操作强制排队,每个设备同时只能有一个写操作,这个故障就消失了。
写操作做互斥,读操作不设限。因为读取设备状态比较轻量,加上锁反而会导致大量查询互相等待,产线看板就废了。
3.4 状态感知与事件总线
Agent 需要知道整个世界发生了什么,这个“知道”不是随时去轮询设备,而是通过事件总线接收订阅的消息。我们用 Redis Streams 做了一版轻量事件总线,设备状态变化、任务完成、异常报警都会发布事件。
OpenClaw 里配置事件订阅的核心思路,是把事件变成“上下文的边际补充”。正常情况下,事件消息不会自动进入模型的对话上下文,而是先落到本地状态存储。只有当模型要回答某个涉及状态的问题时,才通过工具去查询存储。这样避免了“设备每三秒发一次状态,Agent 每三秒被刷新一次记忆”的灾难。
我做的状态快照维护代码如下:
class StateSnapshot: def __init__(self, ttl=30): self.cache = {} self.ttl = ttl def update(self, device_id, state_data): self.cache[device_id] = { "data": state_data, "ts": time.time() } def get(self, device_id): item = self.cache.get(device_id) if not item: return None if time.time() - item["ts"] > self.ttl: return None return item["data"]TTL 设 30 秒,超过 30 秒没有收到新状态的事件,就认为这个状态失效,模型需要主动去读一次最新值。这样做既保证了实时性,又避免了设备心跳产生的频繁刷新把 Token 烧光。
3.5 权限与安全机制
产线控制不能只靠模型自觉,必须从制度上把风险锁死。我们设计了三级权限体系:第一级是查询权限,任何人包括模型都可以调用,只能读设备状态;第二级是诊断权限,允许做参数的临时修改,但必须记录原因;第三级是控制权限,可以启停设备、修改生产参数,这个权限只授给经过认证的操作员。
实际落地时,我们把操作员身份绑定到对话会话上。操作员登录时拿到一个短期令牌,Agent 调用工具时会把令牌传给网关,网关解析出操作人身份和权限等级,然后才决定是否放行。
最严格的约束是:所有写操作必须携带工单号。操作员说“把3号线停了”时,Agent 会反问一句“请提供审批工单号”,没有工单号直接拒绝执行。一开始操作员觉得烦,但真出过一次事故之后,所有人都认同了这个设计。那次是有人误下了停机指令,要不是工单号对不上被网关拦下来,整条线白干半天。
3.6 部署与联调实录
整套系统真正跑通,比我预估的时间多花了三倍。原因在于大部分时间不是在写代码,而是在“教”模型理解产线的各种潜规则。比如“出胶量调大 10%”里的 10% 是相对当前值的比例还是绝对值的百分比,这个歧义问题,我们最后是通过在工具定义里加 “quantity_type”: “absolute” 或 “relative” 字段解决的。
联调阶段约等于在产线旁建了个“模拟沙箱”,用软件模拟 PLC 的响应。操作员先在模拟环境里试用,语言表达五花八门,我把常见表达方式整理成了回归测试集,每次调整框架配置就往里灌一遍,防止改一个 bug 引出三个新 bug。
这里也顺带提一下硬件环境:模拟环境跑在一台高性能工作站上,CPU 32 核,内存 64GB,GPU 用的是消费级显卡做推理加速。真实产线环境中建议把 Agent 服务部署在独立的小型 GPU 服务器上,或者干脆调用集中式推理 API,不要在车间里摆一堆发热的大盒子。
4. 实测效果与量化分析
4.1 核心指标变化
系统在试运行一个月后,我对几个关键指标做了对比统计。
| 指标 | 改造前(人工操作) | 改造后(OpenClaw 指挥) | 变化幅度 |
|---|---|---|---|
| 指令平均响应时间 | 约 45 秒 | 约 6.5 秒 | 提速约 85% |
| 误操作率(万次) | 2.1 次 | 0.3 次 | 下降约 86% |
| 操作员培训时长 | 5 个工作日 | 2 小时 | 大幅缩短 |
| 交接班遗漏项 | 每周 3-4 件 | 每周 0-1 件 | 下降约 80% |
改进幅度最大的是交接班场景。以前操作员交接班要口头传达“3号机今天有点卡顿,记得多看一眼”,Agent 接管后,这类状态信息直接由它汇总成标准记录,每班自动生成一份设备状态简报,新来的操作员不用翻聊天记录,问一句“今天有什么异常”就能拿到完整答案。
4.2 让我意外的表现
说实话,Agent 的表现比预期好的是它在“组合指令”上的能力。操作员说“等2号线这批加工完,把温度上限提两度”,这是一个典型的条件触发任务。OpenClaw 的任务规划器会把它拆成两步:第一步等待“2号线批次完成”事件,第二步执行“修改温度上限”操作。这放在传统自动化系统里要写状态机,在智能体里只需要自然语言描述就可以。
另一个惊喜是模型能根据设备反馈做出简单的“自主判断”。有一次点胶机的胶量曲线出现异常波动,模型不是在机械地执行指令,而是在工具结果里发现了“数值偏差异常”的标记,主动暂停了任务并通知操作员检查喷嘴。这种能力对产线来说非常有价值。
不过它也有明显搞不定的地方:多设备联动的复杂工艺优化,比如“同时调整三段传送带速度让节拍达到最优”,模型给出的方案往往不是最优解。这种问题本质上是运筹优化,不是语言理解能覆盖的。我的处理是明确划分边界:涉及工艺参数联调的场景不让 Agent 乱碰,由工程师人工完成,Agent 只做执行辅助。
5. 常见问题与排查技巧实录
5.1 模型“一本正经地胡说八道”
最典型的问题是模型自作主张补全信息。操作员说“把指示灯调亮一点”,模型默认灯亮度支持从 0 到 100 的整数,直接执行了 brightness=60,但实际上那台设备的指示灯只有“亮/灭”两种状态。出现这种情况的根源是设备接口的枚举范围没有写进上下文。
解决办法是我在每次工具调用前都强制注入设备参数元数据。模型可以看到“该设备支持哪些值和格式”,它就无法自己发挥。另外,所有超出常规范围的操作,比如修改关键安全参数,都要触发二次确认流程:Agent 先把指令草案发给操作员,等操作员回复“确认”再执行。这个过程虽然多了一步,但大幅降低了误操作风险。
5.2 并发指令互相打架
一线操作员通常不是一个人,一个班次可能同时有五六个人在发指令。Agent 内部的多任务调度如果管理不好,会出现多个任务同时写一个设备参数的情况。我们遇到过最离奇的一次是两个人同时调整同一段传送带的电机频率,一个往上调,一个往下调,网关收指令收到手软,设备最终停在了一个两秒内来回摆动的状态。
排查下来发现是 OpenClaw 的任务调度器默认是并行的,不同用户发起的任务会各自独立规划、独立执行。解决办法是在网关层加了一个以设备 ID 为 key 的全局互斥锁,同时把 Agent 内部的任务调度模式改成“同一设备串行化”。我后来也养成了习惯:任何设备写操作上线之前,先在沙箱里用并发测试脚本打一轮,别等到现场出问题再后悔。
5.3 上下文太长导致模型“失忆”
系统跑了一周后,我发现模型经常把 20 轮以前的指令当成当前任务来执行,排查中发现是历史对话没清理干净。OpenClaw 自带的滑动窗口对短任务没问题,但产线这种长时间运行的场景,窗口一久就把旧指令混进来了。
定位方法是在日志里加了“上下文快照”功能,每次模型调用时记录当前上下文里所有消息的时间戳和来源,回查时能看到模型到底看了哪些内容。最后我把上下文窗口从“保留最近 20 轮”改成了“最多保留 8 轮对话加一份固定格式状态摘要”,模型的有效记忆反而更稳定了。
这是一个很有意思的教训:上下文越长不一定越好,对于执行类任务,超过一定长度的上下文会成为噪声源。保持上下文的“干净和新鲜”比盲目增加窗口长度有用得多。
5.4 事件风暴被“翻译”成错误行为
产线设备可以高频推送状态事件,尤其是有过流报警的时候,一个报警事件会被多个设备重复转发。Agent 如果订阅了所有事件,容易被同名事件反复触发操作。我们遇到过一次变频器过流报警,事件总线一秒钟内推送了三十多条相似消息,Agent 看到几十次“过流”,就开始怀疑自己的状态判断,连续发了好几条“需要关机吗”的确认请求,把操作员搞得很烦。
后来我在事件总线入口加了去重和降噪,同一设备同一事件在 5 秒内只允许进入一次上下文。同时把事件的 severity 分级,只有 critical 级别的事件才允许打断 Agent 当前任务,warning 和 info 级别全部进日志,不打扰对话流程。
5.5 模型调用工具时参数格式错误
这是最“细碎”但也最频繁的坑。模型的 language model 擅长生成自然语言,但输出严格的 JSON 时偶尔会把数字写成字符串,比如 {"speed": "0.8"} 而正确应该是 0.8。框架调用工具时若没有做强制类型转换,就会被底层接口拒绝。
我的做法是写了统一的类型收窄层,比如 int 和 float 严格区分,枚举值做模糊匹配后再标准化。比如 “开”和 “on” 会被收窄为 True,而不是原样传下去。数值溢出检测也必须做,有次模型把温度目标值生成了 999,要不是前端校验拦住,加热器就直接满功率上了。
5.6 权限验证与审计追踪的经验
产线操作不能“无痕”,这是底线。每次 Agent 调用写指令,我都会生成一条完整的审计记录,包含操作人、操作时间、目标设备、指令内容、设备反馈、最终结果。整个审计记录通过网关写入不可篡改的日志库。
有一次排查一个工艺参数漂移问题,就是靠着审计记录回溯到三天前某个操作员手动微调了参数且没有记录。从这个角度讲,Agent 反而让过程更透明了——它的每一次操作都有迹可循,比人工口头传达可靠得多。
6. 写在最后的个人体会
整个项目做下来,我最深的感触是:大家总以为“从对话到操控”的难点在模型能不能听懂人话,实际上难点在工程侧——如何把模型输出的“意图”安全、可靠、可追溯地变成物理动作。模型写错一个参数可以重来,但设备执行错一个指令就是代价。
OpenClaw 这类框架确实把“智能体”的开发门槛拉低了很多,但真正决定上限的仍然是接入方的工程能力。在动手之前把权限边界、互斥锁、审计日志、事件降噪这几件事想清楚,会让后续的落地顺畅得多。如果你也想做类似的“AI + 工业控制”项目,建议从一个小场景切入:选一台不关键的风机或者泵,先让它学会听懂开关和调速,跑稳了再做复杂设备的联动。
最近我还在琢磨怎么把工艺参数的自适应调整也交给这个“产线指挥官”。等有阶段性成果了,我再来写一篇续篇。