最近在一个汽车零部件 Tier 1 工厂的 AI 项目里反复处理同一类问题:Agent 既要回答这张生产订单的物料齐套了吗(SAP 侧问题),也要回答3 号线刚才那个停机是什么原因(MES IoT 侧问题),还要回答这批延期交付到底是物料问题还是产线问题(两边都要)。第三类问题最难。它要求 Agent 在 IT 和 OT 两个语义体系之间自由穿梭,而这两个体系的对象定义、时间粒度、数据可信度都不一样。这篇记录我们处理这件事时的几个工程决策,以及踩过的坑。一、问题不是接口打不通,是语义对不上最开始我们以为这是个集成问题:SAP 那边给 OData,MES 那边给 REST,IoT 平台给 MQTT,把数据抽到一个统一存储,Agent 想问什么就查什么。第一周就发现不对。Agent 在回答PO 4500012345 的物料齐套情况时,把 SAP 里的Material 10-2031和 MES 里同名的Item 10-2031当成了同一个东西。实际上,SAP 里这个物料号指的是成品装配后整体,MES 里同样的编码指的是装配前的半成品——同一个号,两套语义。类似的不一致出现在很多地方:SAP 里的生产订单是 PP 模块的Production Order,MES 里的工单是排到具体班次和设备的执行任务,一张 SAP 订单常常对应 MES 里几十张工单SAP 里的完工意味着 GR(收货过账),MES 里的完工意味着设备状态从 RUNNING 转到 IDLE,两者在时间上经常差几个小时甚至一两天SAP 里的工时是工艺路线上的标准工时,MES 里的工时是 IoT 实时采集的实际节拍——它们在数学上不是一回事Agent 拿这些数据做推理,等于在两套坐标系里同时画图。二、第一次重构:在 Agent 之前加一层语义映射最初的想法是让 Agent 自己处理这种不一致——在 system prompt 里告诉它SAP 的物料和 MES 的物料可能不是同一个东西,你要小心。这种做法和我之前在另一个银行项目里走过的弯路一模一样:约束写在 Prompt 里在概率上一定会被绕过。第二次设计在数据层和 Agent 之间加了一个 SemanticBridge,把跨系统的对象映射、单位换算、状态翻译统一处理:pythonclassSemanticBridge:IT/OT 语义映射层。Agent 拿到的不是原始 SAP/MES 数据, 而是已经在统一语义下对齐过的视图。# 对象映射:同一个业务对象在不同系统里的标识OBJECT_MAPPING{production_order:{sap:{table:AUFK,key:AUFNR},mes:{table:WORK_ORDER,key:PARENT_ORDER_NO},relation:one_to_many,# 一张 SAP 订单对应多张 MES 工单},material_finished:{sap:{field:MATNR,filter:MTART FERT},mes:{field:item_code,filter:item_type FINISHED},relation:one_to_one,},# 注意:半成品在两边语义不同,不建立映射,Agent 必须显式指定}# 状态翻译:同一个业务事件在不同系统里的表达STATUS_MAPPING{order_completed:{sap:lambdar:r.get(SYSTEM_STATUS)TECO,# 技术完成mes:lambdar:r.get(status)FINISHEDandr.get(qty_done)r.get(qty_plan),},}defresolve(self,business_object:str,source:str,identifier:str)-dict:统一入口:给业务对象名 来源系统 ID,返回跨系统对齐后的视图mappingself.OBJECT_MAPPING.get(business_object)ifnotmapping:raiseValueError(fObject {business_object} not in semantic dictionary)primaryself._fetch_from(source,mapping[source],identifier)# 按 relation 决定怎么联动其他系统ifmapping[relation]one_to_many:relatedself._fetch_related(business_object,primary,target_systems[mes])return{primary:primary,related:related}return{primary:primary}关键变化:对象映射是声明式的,新增一个跨系统对象要走代码评审,不是 Agent 自由发挥语义不一致的对象不建立默认映射(比如半成品),强制 Agent 显式指定来源,避免看起来能对上导致的错误聚合状态翻译用函数,不用字段映射表,因为很多完工判定是组合条件,不是简单字段比对三、第二次踩坑:时间粒度对不齐SemanticBridge 上线两周后,Agent 在回答昨晚 3 号线的停机原因时给出了一个让产线主管很困惑的答案:它说停机是因为前序工序物料未齐套,但产线主管很确定那个时段物料是齐的——SAP 里的 GR 过账在停机前两小时已经完成。查了半天才发现是时间粒度的问题:SAP 的物料齐套状态是按凭证过账时间刷新的,粒度是分钟级,但实际可用是过账后下一次 MRP 运行才生效,MRP 在这个厂是每 4 小时跑一次MES 的停机事件是 IoT 实时采集的,粒度是秒级Agent 拿到 SAP 数据时看到的是物料齐套已完成,但那个时刻这条信息在 MES 视角下还没生效Agent 的回答从数据角度看没错——SAP 在停机时刻确实显示齐套。但从业务角度看是错的——那个齐套状态对产线还没可见。第三次设计在 SemanticBridge 里加了时间对齐策略:pythonclassTemporalAlignment:跨系统时间对齐策略。Agent 查询时必须指定一个参照系, 数据按该参照系的时间粒度返回,而不是各系统的原始时间戳。REFERENCE_FRAMES{shop_floor:{# 以车间执行视角为参照base_granularity:minute,sap_data_lag:next_mrp_run,# SAP 数据延迟到下一次 MRP 才可见mes_data_lag:real_time,iot_data_lag:real_time,},planning:{# 以计划视角为参照base_granularity:hour,sap_data_lag:real_time,mes_data_lag:next_shift,# 计划视角下 MES 数据按班次聚合iot_data_lag:next_shift,},}defalign(self,query_time,reference_frame:str,system:str,raw_timestamp):frameself.REFERENCE_FRAMES[reference_frame]lag_ruleframe[f{system}_data_lag]returnself._apply_lag(raw_timestamp,lag_rule,query_time)Agent 在查跨系统数据时必须先声明参照系。回答昨晚停机原因这种问题时参照系是shop_floor,SAP 数据会按下一次 MRP 才可见的规则做延迟,Agent 看到的物料齐套状态就和产线视角一致了。这一层加上之后,跨系统因果推理的错误率从抽样 30% 多降到了个位数。具体数字我不太敢公开报,毕竟样本量也就一两百条。四、几个不那么教科书的取舍1. 要不要把所有 OT 数据都接进来?不要。IoT 一条产线一天能产生几千万条传感器数据,全接 Agent 既贵又没用。我们的做法是只接两类:事件级数据(状态切换、报警、停机)和节拍级聚合(每分钟产量、每小时 OEE)。原始秒级数据留在时序库里,Agent 需要时通过工具调用按需查询,而不是塞进上下文。2. SAP 侧用 OData 还是直读底表?这个争议很大。OData 是标准方式,但性能差、字段覆盖不全(尤其是定制 Z 表)。直读底表性能好,但绕过了 SAP 的权限控制和业务校验。我们最终是分两层:Agent 默认走 OData(走 SAP 的权限和校验),只读类批量分析走只读副本(通过 SLT 复制到一个独立的分析库,Agent 在这个库上没有写权限)。3. Agent 要不要能反过来写 SAP/MES?这是争议最大的点。最初业务部门希望 Agent 能直接创建生产订单、调整工单优先级。我们最后的处理是:Agent 只生成待审批的操作建议,不直接写。建议落到一个工单池里,由现场调度或计划员一键确认后再触发实际操作。这个设计被业务诟病还不够智能,但我们认为是当前阶段必须的——Agent 写错一张 SAP 订单可能影响几十万的物料调拨,这个代价不能交给概率性系统承担。4. 工艺知识怎么注入?SAP/MES 数据是事实,但 Agent 要做归因推理还需要工艺知识(比如这台设备振动值超过 X 通常意味着轴承磨损)。我们没有用大而全的工业知识图谱,而是按车间为单位维护一个轻量的工艺规则库,几百条规则,工艺工程师可以直接编辑。RAG 在这个规则库上检索,作为 Agent 推理时的工艺背景。这个方案的扩展性一般,但对单车间够用,也最容易让工艺工程师认账——他们能看到自己写的规则被 Agent 用上了。五、回头看如果让我把这段经历压成一句话:IT/OT 打通的核心不是接口,是语义和时间。接口层的活两周能搞完,语义层和时间层的活两个月还在打补丁,而后者才决定 Agent 的回答能不能让产线主管认账。这件事还有一个反直觉的地方:大模型再强,也补不了语义层的窟窿。Agent 不会自己识别出SAP 物料和 MES 物料是两个东西,也不会自己识别出SAP 数据虽然已经过账但对产线还不可见——这些都是工程师必须显式建模的约束。模型擅长的是在干净的语义层上做推理,不擅长在脏数据上自己想清楚什么是脏。这套设计还有很多没考虑周全的地方——比如多车间共用一套 SAP 时的语义分歧、不同 MES 版本升级后的对象映射回归、Agent 在历史数据回溯查询时的时间对齐准确性。这些慢慢补。但在 Agent 前面建一层语义和时间的对齐这条基本原则,目前看是站得住的。