SAP S/4HANA 与车间 MES 的 Agent 上下文打通:语义层与时间粒度的几个工程决策
最近在一个汽车零部件 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,把跨系统的对象映射、单位换算、状态翻译统一处理:
python
class SemanticBridge:
"""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": lambda r: r.get("SYSTEM_STATUS") == "TECO", # 技术完成
"mes": lambda r: r.get("status") == "FINISHED" and r.get("qty_done") >= r.get("qty_plan"),
},
}
def resolve(self, business_object: str, source: str, identifier: str) -> dict:
"""统一入口:给业务对象名 + 来源系统 + ID,返回跨系统对齐后的视图"""
mapping = self.OBJECT_MAPPING.get(business_object)
if not mapping:
raise ValueError(f"Object '{business_object}' not in semantic dictionary")
primary = self._fetch_from(source, mapping[source], identifier)
# 按 relation 决定怎么联动其他系统
if mapping["relation"] == "one_to_many":
related = self._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 里加了时间对齐策略:
python
class TemporalAlignment:
"""跨系统时间对齐策略。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",
},
}
def align(self, query_time, reference_frame: str, system: str, raw_timestamp):
frame = self.REFERENCE_FRAMES[reference_frame]
lag_rule = frame[f"{system}_data_lag"]
return self._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 前面建一层语义和时间的对齐"这条基本原则,目前看是站得住的。
浙公网安备 33010602011771号